Cyberpunk 2077 Quest Authoring
The RED Questbook
Quest modding in Cyberpunk 2077 is powerful, but the public documentation is still scattered across a small number of focused guides. This book provides a standalone path from opening your first quest resource to building quests with journal entries, world interactions, characters, scenes, combat, vehicles, and cleanup.
You do not need to understand REDengine internals before you begin. The early chapters introduce one idea at a time, and the downloadable WolvenKit projects give you something concrete to inspect whenever the resource model feels abstract.
The book complements the broader REDmodding quest and scene guides. It lives separately because native quest authoring is still developing quickly and benefits from executable examples, coordinated diagrams, and a learning path that can evolve as the tools improve.
Start small
Begin with Start here. You will install the tools, learn the shape of a WolvenKit project, inspect one vanilla questphase, and build First Signal: a quest with one objective, a short wait, and a one-shot completion fact.
Five incremental labs then add the major systems:
- First Signal — questphase, journal, localization, and a persistent fact.
- Signal Race — conditions, waits, Boolean trees, and parallel signal flow.
- Boundary Check — streaming sectors, NodeRefs, triggers, and a map pin.
- Handoff Point — root and child questphases with explicit input/output contracts.
- First Contact — a community actor, readiness, a spoken scene, named handoff, and delayed cleanup.
Each lab includes an overview, a manual WolvenKit walkthrough, a completed project, and an in-game test procedure. You can follow them in order or use the lab overview to find the closest starting point for your own quest.
Think in cooperating resources
A Cyberpunk quest is rarely one file:
.questphasegraphs coordinate progression and persistent state;.journalresources define quests, objectives, messages, files, and map pins;- localization resources provide UI text, subtitles, dialogue, and choices;
- streaming resources place triggers, markers, communities, AI spots, and devices in the world;
.sceneresources provide dialogue, choices, events, animations, and named exits;- entity, appearance, and TweakDB resources provide custom characters and gameplay records.
Most difficult bugs occur where two of these owners disagree. The book therefore explains not only which properties to set, but which resource owns the behavior, how identities connect across files, and what to inspect when a graph looks valid but nothing happens in game.
What the examples prove
The downloadable projects are executable reference material, not magic templates. Their structure is checked with the version of WolvenKit listed in Tested versions, and every supplied node and resource is explained in the book.
All five labs are Structurally validated. Their in-game acceptance runs are still pending, so their intended runtime behavior remains Experimental. Use the test guides to check what happens in your game; the completed downloads are comparison projects, not a promise of a tested playthrough.
The system chapters explain the resource relationships, and the test guides show how to check their behavior in game.
Start here
This section takes you from a verified Windows game installation and no WolvenKit project to an installed, observable quest mod. It assumes no previous REDengine knowledge and no GitHub, Nexus Mods, or WolvenKit account.
The first result is deliberately small: First Signal activates one quest and one objective, waits ten real-time seconds, completes them, records a one-shot fact, and terminates. It has no world, scene, NPC, device, or audio dependency.
The supplied start and completed projects let you compare your work at each stage. The completed resources pass the book’s structural checks; the test guide shows you how to verify the player-facing result in your own game.
What a quest consists of
A .questphase is the coordinator, not the entire quest. Even this minimal
example crosses four ownership boundaries:
| Responsibility | Owner in Lab 1 |
|---|---|
| Execution, wait, fact guard, and termination | cqa001.questphase |
| Quest and objective definitions | cqa001.journal |
| English player-facing text | cqa001 onscreen localization resource |
| Attaching those mod-owned resources to game roots | CQA_Lab01_OneShot.archive.xl |
The first three are native CR2W resources packed into a game archive. The last is a loose ArchiveXL framework file. Installing only one layer cannot produce the complete behavior.
Use this reading path
- Install the pinned toolchain from a known starting state.
- Create a project and understand its layers.
- Extract and inspect one named vanilla questphase in a separate research project.
- Read Foundations for ownership, graph execution, identifiers, save-backed state, composition, and lifecycle.
- Build Lab 1: First Signal.
- Install, test, and reset without confusing an old save with a clean test.
Use the tested version set throughout. Newer versions may work, but editor labels, serialization, or framework registration can change; note the versions you actually use when reporting a problem.
What you do not need
You do not need a quest generator, a manifest compiler, a pre-extracted copy of the game, or access to the research repository used to develop this book. You will author and inspect the game resource model with WolvenKit, package native resources in an archive, register them with ArchiveXL, and test them in the game.
You also do not need to edit raw CR2W-JSON. The serialized files supplied with the completed checkpoint exist for review, deterministic diagrams, and structural comparison; the beginner workflow stays in WolvenKit.
Install the pinned toolchain
This procedure establishes one reproducible Windows authoring environment. At the end, WolvenKit can browse your installed game and the framework logs can prove which runtime components loaded.
| Record | Value |
|---|---|
| Game baseline | Cyberpunk 2077 2.31a, Windows GOG build |
| Authoring tool | WolvenKit 8.19.0 |
| Resource framework | ArchiveXL 1.27.0 |
| Runtime loader | RED4ext 1.30.0 |
| ArchiveXL script requirement | redscript 0.5.31 |
| Runtime test date | Not yet recorded |
Version note: the version selection and file layouts below are supported by the linked upstream releases and project documentation. The Lab 1 resources are Structurally validated; installing the prerequisites does not make Lab 1 Runtime-proven.
The repository-wide baseline is recorded in Tested versions.
ArchiveXL’s upstream documentation also lists redscript 0.5.31+ as a
requirement, so this guide fixes the first compatible stable version,
0.5.31, rather than leaving a clean-install reader with a hidden dependency.
Start from a known game installation
You need:
- 64-bit Windows 10 or 11;
- a legally owned Windows copy of Cyberpunk 2077 and the store or launcher account that owns it;
- enough writable storage for WolvenKit, its asset cache, and projects outside the game directory;
- permission to copy the framework and built mod files into the game directory.
No GitHub account is needed to download public release assets. No Nexus Mods, WolvenKit, Discord, or other service account is required for this book.
Launch the unmodded game once and confirm the version shown by the game is the expected build. Then exit completely. If the installation may be damaged, use your storefront’s verification function; CD Projekt RED documents Verify/Repair for GOG and equivalent Steam/Epic checks.
Store verification repairs official files but may leave extra mod files in
place. An installation is a controlled baseline only when you also know what
is present under locations such as archive\pc\mod, red4ext, and
r6\scripts. Preserve an inventory or use a dedicated test installation.
Never delete the whole game directory as a troubleshooting shortcut.
This release was checked against the exact GOG suffix 2.31a. The public patch
name and the labels shown by other storefronts may be 2.31; treat those as a
separate environment until the example is repeated there.
Install WolvenKit 8.19.0
-
Install Microsoft’s .NET 8 runtime. The WolvenKit 8.19.0 release names .NET 8 as its prerequisite.
-
From that release, download
WolvenKit-8.19.0.ziporWolvenKitSetup-8.19.0.exe. Do not download GitHub’s automatically generated “Source code” archives. -
If you chose the ZIP, extract it into a dedicated, user-writable tools directory. WolvenKit’s project documentation treats the ZIP build as a portable application; it does not belong inside the Cyberpunk directory.
-
Start
WolvenKit.exe. On first launch, set Game Executable Path to the actualCyberpunk2077.exe:<Cyberpunk 2077>\bin\x64\Cyberpunk2077.exe -
Set Depot Path to a writable cache directory outside both the game and your projects, for example
C:\CyberpunkModding\WolvenKitDepot. -
Confirm the status bar reports
8.19.0, then close WolvenKit before installing the runtime frameworks.
The upstream download and setup guide and settings reference explain these two paths. The setting called Depot Path is WolvenKit’s local asset cache. It is not a resource’s virtual depot path; that distinction is covered in Project and resource structure.
The separate WolvenKit Console download is not a reader prerequisite for these chapters.
Install the runtime dependencies
All release archives in this section are laid out relative to the Cyberpunk
2077 directory. Extract each archive so that its bin, red4ext, or r6
directory merges with the directory of the same name in the game installation.
Do not create an extra wrapper such as
Cyberpunk 2077\red4ext-1.30.0\red4ext.
- Install the Microsoft Visual C++ 2015–2022 x64 Redistributable required by RED4ext.
- Download
red4ext-1.30.0.zipfrom the RED4ext 1.30.0 release and extract it into the Cyberpunk 2077 directory. Do not use the separate symbols archive. - Download
redscript-v0.5.31-windows.zipfrom the redscript 0.5.31 release and extract it into the same directory. - Download
ArchiveXL-1.27.0.zipfrom the ArchiveXL 1.27.0 release and extract it into the same directory.
The RED4ext installation guide and ArchiveXL repository are the authoritative upstream installation references.
At minimum, the merge should leave these files at these relative locations:
bin\x64\winmm.dll
red4ext\RED4ext.dll
red4ext\plugins\ArchiveXL\ArchiveXL.dll
ArchiveXL also supplies files below r6\config and its plugin directory.
Their presence is expected; do not cherry-pick only the DLL.
Prove the frameworks start
Launch the game once, reach the main menu, and exit. Check for:
<Cyberpunk 2077>\red4ext\logs\red4ext.log
<Cyberpunk 2077>\red4ext\logs\game.log
<Cyberpunk 2077>\red4ext\plugins\ArchiveXL\ArchiveXL.log
<Cyberpunk 2077>\r6\logs\redscript_rCURRENT.log
The RED4ext documentation identifies red4ext.log and game.log as the
loader/plugin checks, while the
redscript repository identifies its
r6\logs output. ArchiveXL writes its own plugin log after it loads.
Record the version banners and resolve errors before creating a quest. A file existing on disk proves only that it was copied; a clean framework log proves only that the loader reached that component. Neither proves a quest resource was registered or executed.
Setup checkpoint
Before continuing, you should be able to answer yes to all of these:
- Does the unmodded game reach the main menu on the target build?
- Does WolvenKit 8.19.0 point to the real game executable?
- Is the WolvenKit cache outside the game and project directories?
- Do RED4ext, redscript, and ArchiveXL create their expected logs without an unresolved dependency or compilation error?
- Have you recorded any unrelated mods still present in the test installation?
If not, stop here. Quest edits cannot repair a broken authoring or runtime baseline.
Project and resource structure
This chapter creates an empty WolvenKit project and establishes the path model
used throughout the book. It also explains why a valid .archive is only one
part of a quest installation.
| Record | Value |
|---|---|
| WolvenKit | 8.19.0 |
| Runtime baseline | Cyberpunk 2077 2.31a, ArchiveXL 1.27.0, RED4ext 1.30.0, redscript 0.5.31 |
| Runtime test date | Not yet recorded |
Version note: the project layout is the WolvenKit 8.19.0 layout used by the supplied checkpoints. Lab 1’s CR2W resources are Structurally validated. This page makes no claim that an untested archive executes in game.
Create the Lab 1 project
Open WolvenKit and use either File > New Project or Create New Project on the home view. Enter:
| Field | Lab 1 value |
|---|---|
| Project name | CQA Lab 01 One Shot |
| Creation location | A writable projects directory outside Cyberpunk 2077 |
| Mod name | CQA_Lab01_OneShot |
| Author | Your name |
| Version | 0.1.0 |
Choose Finish. WolvenKit creates a project directory and a .cpmodproj
file; the source subdirectories are created or populated as the project gains
files. The upstream
WolvenKit project guide
documents the same fields and explicitly warns against creating a project
inside the game directory.
If you prefer the book’s checkpoint, extract the
Lab 1 start download into that projects
directory and open CQA_Lab01_OneShot_Start.cpmodproj. Do not open a project
directly inside the ZIP.
Keep four path domains separate
The word “path” refers to four different things in this workflow:
| Domain | Example | Meaning |
|---|---|---|
| Game filesystem path | C:\Games\Cyberpunk 2077\archive\pc\mod | A location Windows can open |
| Project filesystem path | C:\CyberpunkModding\Projects\CQA_Lab01_OneShot\source\archive | Authoring files owned by this project |
| WolvenKit cache/depot directory | C:\CyberpunkModding\WolvenKitDepot | Extracted/cache data used by WolvenKit |
| REDengine depot path | mod\cqa\cqa001\phases\cqa001.questphase | A resource address in the game’s virtual depot |
Only the last value belongs in a CR2W ResourcePath or an ArchiveXL
registration. Never serialize a drive letter or your personal project
directory as a resource address.
Understand the source layers
The completed Lab 1 checkpoint has this deliberate shape:
CQA_Lab01_OneShot.cpmodproj
source\
├── archive\
│ └── mod\cqa\cqa001\
│ ├── phases\cqa001.questphase
│ ├── journal\cqa001.journal
│ └── localization\en-us\onscreens\cqa001.json
├── raw\
│ └── mod\cqa\cqa001\...
└── resources\
└── CQA_Lab01_OneShot.archive.xl
source\archive contains cooked REDengine resources. The folders beneath it
are their depot paths, so the phase’s project path
source\archive\mod\cqa\cqa001\phases\cqa001.questphase
becomes this virtual address when packed:
mod\cqa\cqa001\phases\cqa001.questphase
source\resources contains loose control files that WolvenKit stages beside
the packed archive. Lab 1’s .archive.xl tells ArchiveXL to attach the
mod-owned questphase below base\quest\cyberpunk2077.quest and merge the
mod-owned journal and localization resources.
source\raw is a working/review layer. The completed checkpoint includes
WolvenKit CR2W-JSON there so the book can produce exact diffs and diagrams.
Those files are not loaded by the game, and raw JSON editing is not the
beginner authoring path.
WolvenKit generates packed as staging output. A normal Install rebuilds
that output and copies its game-relative hierarchy. Do not author in packed:
the tool may delete and recreate it.
CR2W is a container, not a quest language
CR2W is the binary resource container used by REDengine. The extension and root type tell the engine how to interpret a particular container:
| File | Root responsibility |
|---|---|
.questphase | Quest graph, nodes, sockets, handles, phase interfaces |
.journal | Journal entry hierarchy and entry state targets |
localization .json CR2W | Cooked localization entries, despite the filename suffix |
The Lab 1 .questphase has a questQuestPhaseResource root containing a
questGraphDefinition. Nodes inside that graph refer to other resources and
identifiers; they do not embed everything the player sees.
A WolvenKit CR2W-JSON export is a text serialization of the same object model.
It is useful for inspection and deterministic tooling, but its $type, handle,
and buffer structure is not a separate runtime format. Saving JSON-shaped text
with a .questphase extension does not create a cooked resource.
Archives and framework resources solve different problems
Packing turns source\archive into a .archive bundle. The archive preserves
the internal depot paths so the engine can resolve the resources.
ArchiveXL’s loose file is not part of that CR2W bundle. For Lab 1 it contains:
quest:
phases:
- path: mod\cqa\cqa001\phases\cqa001.questphase
parent: base\quest\cyberpunk2077.quest
journal:
- mod\cqa\cqa001\journal\cqa001.journal
localization:
onscreens:
en-us:
- mod\cqa\cqa001\localization\en-us\onscreens\cqa001.json
The .archive answers “does the resource exist at this depot path?” The
.archive.xl answers “under which game-owned root should this resource be
registered or merged?” Both must reach the installation.
After a normal install of the completed checkpoint, expect these two top-level payloads:
<Cyberpunk 2077>\archive\pc\mod\CQA_Lab01_OneShot.archive
<Cyberpunk 2077>\archive\pc\mod\CQA_Lab01_OneShot.archive.xl
Their presence still proves only staging. The archive path list, framework log, game behavior, and save-state matrix are separate checks.
Project checkpoint
Before adding nodes, verify:
- the
.cpmodprojis outside the game directory; - the mod name is unique and stable;
- every cooked resource will live below
source\archiveat its intended depot path; - loose
.archive.xlconfiguration will live belowsource\resources; - nothing below
source\rawis being mistaken for runtime input.
Continue with Inspect a vanilla questphase, then read Resources and ownership before authoring Lab 1.
Inspect a vanilla questphase
Here you will extract one named game resource into a disposable WolvenKit project, inspect its ownership and graph shape, and record observations without shipping CD Projekt RED’s file.
| Record | Value |
|---|---|
| Extraction target | Cyberpunk 2077 2.31a Windows archives |
| Inspection tool | WolvenKit 8.19.0 |
| Runtime test | Not applicable; read-only research procedure |
Vanilla reference: the depot path and focused behavior below are Observed in vanilla. A retained research extraction reported game version
2310, was extracted on 2026-07-24, and was serialized for inspection with WolvenKit 8.17.4. No extracted binary or complete serialization is included in this book. Re-extract it from the pinned2.31ainstallation with WolvenKit 8.19.0 before relying on its current structure.
Why use a separate inspection project
Use a project such as CQA_Vanilla_Inspection, not the Lab 1 project. Adding a
vanilla file to a WolvenKit project places the cooked resource beneath that
project’s source\archive; packing the project would therefore copy the
vanilla resource into a distributable archive.
A separate project creates a simple safety boundary:
- extracted game resources stay local;
- notes can cite their original depot paths;
- tutorial projects contain only mod-owned resources;
- the inspection project is never packaged or committed.
The reference is evidence, not a template. It contains quest-local facts, NodeRefs, journal paths, scenes, devices, and nested phases that will not become valid merely because the file was copied.
Extract the named resource
Create or open the disposable project, then enable the Asset Browser from WolvenKit’s View menu if it is hidden. Ensure Mod Browsing mode is off so the search covers the installed base-game archives.
Search for this exact depot path:
base\quest\side_quests\sq021\phases\sq021_randys_room.questphase
Select the exact result, open its context menu, and choose Add selected items to project. The official Asset Browser guide documents that action and explains that it extracts individual files from installed archives without uncooking the entire game.
Verify that the project now contains:
source\archive\base\quest\side_quests\sq021\phases\sq021_randys_room.questphase
The leading source\archive is project structure. The depot path begins at
base. Do not rename the extracted file before recording its provenance.
If you only want to browse another reference without retaining it, Open without adding to project is the safer read-only route. It does not satisfy this extraction checkpoint because no project copy is retained. The upstream File Editor documentation describes both workflows.
Inspect ownership before graph layout
Open the project copy. Start with the resource tree/properties, then use the quest graph view as an index into exact nodes. Record these facts:
| Question | What this resource shows |
|---|---|
| What is the resource root? | questQuestPhaseResource |
| What owns executable topology? | Its questGraphDefinition and nested phase graphs |
| How can execution enter or leave? | Named input/output node sockets, not the filename |
| Are all dependencies embedded? | No; nodes refer to quest-local facts, journal paths, a computer NodeRef, and a scene |
| Can screen position prove order? | No; socket connections and node semantics prove flow |
Do not take a graph screenshot as your structural record. Write down node ID, concrete type, decisive properties, input/output socket names, and connected node/socket pairs. That record survives editor zoom, layout, and theme changes.
The resource is intentionally large. Do not try to understand all of it on the first pass. The focused computer-document sequence is useful because it shows ownership crossing from a device/scene flow into a fact-backed quest wait:
| Node | Focused observation |
|---|---|
175 | Computer manager enables UI interactivity for #sq021_randy_pc |
176 | Waits for sq021_randy_pc_webfile_found > 0 |
177 | Disables computer UI interactivity |
182 | Activates journal page internet_sites/drugs_are_bad/05_secret_page |
191 | Sets sq021_thisisfucked_read = 1 from the computer-page output |
192 | Waits until sq021_thisisfucked_read > 0 |
193 | Sets the next quest fact after the read signal is observed |
These rows are Observed in vanilla, not a universal recipe. The important boundary is that the authored computer flow emits a dedicated fact and the questphase waits for it. The journal activation alone is not evidence that an arbitrary document-read condition exists.
Record provenance, not the asset
For a research note, retain:
- the exact depot path;
- game and WolvenKit versions;
- extraction date;
- the small node/property excerpt needed for the claim;
- optionally, a local SHA-256 of the extracted file;
- your interpretation and its evidence label.
Do not publish the cooked .questphase, a complete CR2W-JSON export, or a ZIP
containing the inspection project. A hash identifies your local reference but
does not grant redistribution permission.
When the game updates, extract again into a fresh disposable project and compare focused facts. Do not silently carry a previous-build observation forward.
Inspection checkpoint
You are ready to continue when you can explain why:
- the Windows path and depot path are different;
- adding the file to a project extracts a distributable cooked resource;
- a vanilla phase’s external references do not become mod-owned dependencies;
- node sockets, not visual placement, establish execution;
- your notes can support a claim without redistributing the source asset.
Next, read Graph execution and the rest of Foundations.
Foundations
Native quests are not one file format. A questphase coordinates resources that have different owners, identifiers, loading rules, and persistence behavior. The foundations establish those boundaries before a tutorial asks you to edit a graph.
Read these chapters in order:
- Resources and ownership
- Graph execution
- Identifier domains
- Facts, journals, and saves
- Root and child questphases
- Lifecycle and cleanup
Afterward you should be able to answer:
- which resource owns a behavior;
- whether an identifier names an object, a world location, or a lookup key;
- whether a node evaluates now or waits;
- which state survives a save and reload;
- where a parent phase ends and a child phase begins;
- what serialization, packing, and runtime tests each prove.
The chapters use small excerpts from the mod-owned cqa001 example. They cite
vanilla depot paths where useful but do not redistribute extracted game
resources.
Resources and ownership
A questphase is a coordinator. It can change facts and journal state, wait on conditions, start scenes, refer to world objects, and run child phases. It does not make the referenced journal entry, scene, actor, trigger, or localized text exist.
That distinction is the first debugging tool: when a lookup fails, identify the resource that owns the missing thing before changing the graph that refers to it.
The resource layers
| Layer | Typical resources | Owns |
|---|---|---|
| Quest control | .questphase | Execution flow, conditions, journal operations, facts, child-phase and scene handoff |
| Journal and UI | .journal | Quest, phase, objective, description, message, file, and map-pin entries |
| Localization | onscreen JSON resources, subtitle maps, VO maps, embedded scene locStore | Player-facing text through separate lookup systems |
| Scenes | .scene, lipsync and animation references | Dialogue, choices, performers, timed events, entry points, and named exits |
| World | streaming blocks, streaming sectors, inplace resources, .devices | Triggers, markers, communities, AI spots, devices, and NodeRef registration |
| Characters | .ent, .app, TweakDB records, community entries | Entity composition, appearances, gameplay records, and spawned identities |
| Framework registration | ArchiveXL and related loose configuration | Merging or registering mod-owned resources with game roots |
One player-facing beat can cross several rows. “Reach the marker and talk to a contact” needs at least a questphase, journal entries, localization, world markers and triggers, a community, and a scene. No one row silently supplies the others.
Depot paths
A depot path is the resource’s address inside the game’s virtual resource space:
mod\cqa\cqa001\phases\cqa001.questphase
The same address appears in several contexts:
- as a file beneath a WolvenKit project’s
source\archive; - as a
ResourcePathvalue inside another CR2W resource; - as an ArchiveXL registration path;
- as a path printed in packing or runtime logs.
Use the spelling and backslashes shown by WolvenKit. A Windows filesystem path
such as H:\mods\... is not a depot path and must never be serialized as the
resource address.
The mod\cqa\... prefix means that the example owns the resource. A reference
to base\quest\cyberpunk2077.quest points to a vanilla resource; it does not
copy that resource into the project.
Ownership versus reference
Owning and referring are different:
cqa001.questphase
refers to journal path quests/minor_quest/cqa001
cqa001.journal
owns the cqa001 journal entry
cqa001 journal entry
refers to localization key cqa_cqa001_title
cqa001 onscreen localization
owns the English text First Signal
Changing the questphase cannot repair a missing localization key. Changing the localization resource cannot activate an objective. Follow the lookup to its owner.
WolvenKit project layers
The examples use this source layout:
source/
├── archive/ CR2W resources at their intended depot paths
├── raw/ serialized review artifacts for mod-owned CR2W resources
└── resources/ loose ArchiveXL and other framework configuration
source\archive and source\resources have different installation
destinations. Packing the CR2W tree does not automatically prove that the loose
ArchiveXL file was staged, and seeing a valid .archive does not prove that
the root questphase was registered.
The source\raw copies support review, round trips, and deterministic diagrams.
They are documentation-author artifacts, not an instruction to make raw JSON
editing the beginner workflow.
Lab 1 ownership map
Lab 1 deliberately stops at three native resources:
| Outcome | Owner |
|---|---|
| Run once, wait, and finish | cqa001.questphase |
| Define the quest and objective | cqa001.journal |
| Display “First Signal” and “Wait for the signal.” | cqa001 onscreen localization |
| Attach all three to game roots | CQA_Lab01_OneShot.archive.xl |
It has no world resource. That is why player position is irrelevant to Lab 1.
Diagnostic questions
When a feature is absent, ask in this order:
- Does the owning resource exist at the exact depot path?
- Was that resource packed or staged in the correct installation layer?
- Was it registered or merged where required?
- Does the referring resource use the correct identifier domain?
- Does runtime state permit this resource to activate now?
A graph edit belongs at step five only when the first four answers are already known.
Graph execution
A questphase graph is a set of node definitions connected through named sockets. Execution enters a node through an input socket, the node performs its operation or evaluates its condition, and execution continues through one or more output sockets.
The graph is not executed according to the order in which nodes happen to appear in serialized JSON or in a WolvenKit list.
Nodes, sockets, and connections
Every quest node has a graph-local numeric id and an array of socket handles.
A socket has:
- a name such as
In,Out,Active,Succeeded,True, orFalse; - a direction or role such as
Input,Output, orCutDestination; - zero or more graph connections.
A connection joins one source output socket to one destination input socket:
node 12, Out ──────────> node 13, In
The socket names carry semantics. Sending execution into Active on a journal
node activates an entry; sending it into Succeeded changes that same entry to
a different state. The node type and path can be identical while the incoming
socket changes the operation.
Entry and termination
A small child-compatible phase commonly exposes:
questInputNodeDefinition socketName: In1
...
questOutputNodeDefinition socketName: Out1
type: Terminating
In1 and Out1 are the phase interface. Internal node sockets can still be
named In and Out.
A terminating output is an explicit lifecycle boundary. Reaching the last gameplay node is not the same as terminating the phase unless an edge reaches the output node.
Evaluate now versus wait
The two most important condition shapes have different control semantics.
questConditionNodeDefinition
This node evaluates when execution reaches it and immediately chooses a named
output such as True or False.
Lab 1 uses it for:
cqa001_completed == 0
True -> activate the quest
False -> terminate
It does not wait for a false condition to become true.
questPauseConditionNodeDefinition
This node holds its execution path until the condition becomes true, then emits
Out.
Lab 1 uses a questRealtimeDelay_ConditionType for ten seconds. Later chapters
use pause conditions for facts, triggers, actor readiness, inventory, and
other wait-until gates.
Replacing one condition node type with the other changes the quest’s behavior even if their nested comparison data looks identical.
Fan-out, joins, and ordering
One output can connect to several inputs. That starts several paths; it does not establish a secret order among them.
arrival
+-> update objective
+-> start hostility setup
If later work requires both paths, use a node with explicit join semantics. If only the first signal should win, use a race/XOR shape. Do not use visual proximity or JSON array order as a substitute for control flow.
An ordered sequence needs edges:
activate community
-> wait for spawn readiness
-> start scene
Starting all three from one fan-out would permit the scene to begin before its actor is ready.
Ordinals are local semantics
Some scene and quest node types attach ordinals to ordered sockets or options. An ordinal belongs to that node type’s local contract; it is not the execution order of the entire graph.
For example, Observed in vanilla, scene choice nodes use option socket
ordinals and additional padded sockets. That rule does not mean every quest
output socket needs an ordinal or that node ID 12 executes before node ID
13.
Preserve ordinals when copying a verified node shape. Explain their purpose before generating or renumbering them.
Cut and interrupt sockets
Many graph nodes contain a CutDestination socket in addition to their normal
inputs and outputs. It belongs to cancellation or interruption routing, not to
ordinary success flow.
The presence of the socket does not mean every tutorial should wire it. Interruption policy must answer:
- what work is currently active;
- what journal or world state must be cleaned up;
- whether a scene or child phase must receive a cut;
- which output tells the parent that interruption is complete.
Until a guide proves those answers, an unwired cut socket should be described as a visible lifecycle obligation rather than filled with a guessed edge.
Reading the Lab 1 graph
Trace the exact Lab 1 figure by following socket labels:
In1emitsOutinto the one-shot guard’sIn.Trueenters the quest journal node throughActive.- Each ordinary
Outadvances the first-run sequence. - The delay’s
Outenters the objective throughSucceeded. - The final quest journal operation enters through
Succeeded. - Both the guard’s
Falseroute and the successful route reach the same terminating output.
That trace explains behavior without relying on how WolvenKit arranged the boxes.
Identifier domains
Cyberpunk resources place many identifier types beside one another. Similar text or numbers do not make them interchangeable.
Domain map
| Domain | Typical serialized shape | Identifies |
|---|---|---|
| Depot path | ResourcePath | A resource in the virtual depot |
| CR2W handle | HandleId / HandleRefId | An object inside one serialized CR2W object graph |
| Quest graph node | numeric node id | A node inside one questphase graph |
| World reference | NodeRef | A registered world or quest-prefab object |
| Name token | CName | A REDengine name value such as a socket or entry-point name |
| Gameplay record | TweakDBID | A record in TweakDB |
| Fact | string factName | A signed integer state slot in the facts database |
| Journal path | gameJournalPath.realPath | An entry in the merged journal tree |
| Localization key | secondary key or numeric locstring ID | Text in one of several localization lookup systems |
| Scene-local IDs | actor, performer, node, event, screenplay, locstring, and variant IDs | Distinct objects inside scene systems |
Never derive one domain from another merely because the values can both be printed as strings or integers.
CR2W handles
A CR2W handle gives one serialized object an identity:
{
"HandleId": "15",
"Data": {
"$type": "questFactsDBManagerNodeDefinition",
"id": 15
}
}
Another field can refer to that object:
{ "HandleRefId": "15" }
The graph node id and HandleId happen to both be 15 in this shortened
example, but they serve different domains and are not required to match.
Handle rules:
- a
HandleRefIdmust resolve to one compatibleHandleId; - handle identity is local to the serialized CR2W object graph;
- duplicating a handled object without repairing its internal references can create dangling or aliased structure;
- Structurally validated: WolvenKit round trips can normalize handle allocation while preserving the same node/socket graph.
For this reason, exact diagrams fingerprint semantic nodes, sockets, and edges, not the incidental handle numbers assigned by one serialization pass.
NodeRefs
A NodeRef identifies something registered in the world or beneath a
quest-prefab root:
#cqa_example_trigger
$/mod/cqa/#cqa_example_prefab/#cqa_example_trigger
It is not a CR2W handle. A valid-looking NodeRef string still fails if no loaded streaming resource registers that reference or if the questphase does not declare the required prefab dependency.
Later world chapters explain:
- streaming-sector
nodeRefs; QuestPrefabRefHash;- streaming-block
questPrefabNodeRef; - questphase
phasePrefabs; - full versus local NodeRef paths.
Lab 1 contains no NodeRef, which is why it has no position or streaming requirement.
Journal paths
A journal path names a position in the merged journal tree:
quests/minor_quest/cqa001/cqa001_01/cqa001_01_obj_wait
The accompanying fileEntryIndex identifies the path component that belongs
to the containing gameJournalFileEntry. For the Lab 1 path it is 2, the
zero-based position of cqa001:
0: quests
1: minor_quest
2: cqa001
It is not the objective’s index, a graph node ID, or a CR2W handle.
Names are typed
Plain text in WolvenKit can represent several serialized types:
"Out1" CName
"mod\cqa\...\cqa001.questphase" ResourcePath
"#cqa_example_trigger" NodeRef
"Items.ExampleItem" TweakDBID
"cqa001_completed" fact name
Record the type beside a value when documenting it. “Set this field to
cqa001” is incomplete if the reader cannot tell whether the field expects a
path, CName, fact, journal ID, or record ID.
Scene IDs are not one pool
Scenes add several numeric domains: scene graph nodes, actors, performers, screenplay items, localization strings, localization variants, and timed events. Observed in vanilla patterns can constrain one of those domains, but they do not authorize reusing the same number across all of them.
The scene section will document each domain when the corresponding object is introduced. Until then, treat “scene ID” as an ambiguous phrase that needs a type.
Facts, journals, and saves
A quest graph does not start from a blank state every time an archive changes. Facts, journal state, active quest work, checkpoints, scenes, communities, and devices can outlive the game session that created them.
That makes the save part of the test input.
Facts are integer state
Quest facts are signed integer values addressed by name. In the tested quest
fixtures, an unset fact reads as 0.
Common conventions:
| Value shape | Meaning chosen by the author |
|---|---|
0 / 1 | false/true, not started/completed, inactive/active |
| increasing integer | stage number, counter, or threshold |
| distinct named facts | independent events that must not overwrite each other |
The engine does not know that _completed means completion. The graph gives
the value meaning by deciding when to read and write it.
Prefer event-specific names:
cqa001_completed
cqa002_offer_sent
cqa002_job_accepted
One overloaded quest_state fact makes re-entry and failure diagnosis harder
because unrelated transitions erase one another.
Snapshot and wait semantics
The same fact comparison can support two behaviors:
Condition:
evaluate cqa001_completed == 0 now
choose True or False
PauseCondition:
hold this path until cqa001_completed == 0
Choose the node according to control semantics, not according to the fact family.
One-shot protection
Lab 1 uses:
if cqa001_completed == 0:
run quest
else:
terminate
The fact is set before the quest reaches its successful terminating output. On
the next root evaluation, the False branch terminates without reactivating the
journal.
Runtime testing must still cover interruption between the fact write and quest completion. A graph that looks one-shot on the happy path can expose partial state after a save, crash, reload, or cut.
Journal state is also persistent
Journal entries can retain states such as inactive, active, succeeded, or failed. Some entry families also retain visited state.
These are separate questions:
- Is the entry active?
- Has the player visited or opened it?
- Has its objective succeeded?
- Is the quest currently tracked?
Do not substitute a visited condition for an objective state or assume that activating an entry proves the player saw its presentation.
Other save-backed systems
Research fixtures have demonstrated persistence involving:
- quest facts;
- journal activation and visited state;
- active questphase branches and checkpoints;
- scene state;
- community and actor lifecycle;
- device persistent state associated with a NodeRef.
A newly packed CR2W resource cannot erase the older state already stored in a save. Device tests may require both a clean save and a fresh NodeRef identity when deliberately invalidating a previous device instance.
What “clean save” means
For a focused quest start test, use a manual save created before any version of that quest mod was installed or registered.
Avoid:
- an autosave created after the quest first activated;
- a save made after a failed graph or scene probe;
- a save where the journal entry was already visited;
- a save that streamed an older version of a persistent device.
Resetting one fact is useful for diagnosis, but it does not clear every journal, checkpoint, scene, community, or device record. A console reset is therefore not equivalent to a clean save.
Minimum save test matrix
For a one-shot quest:
- First load: start from the known pre-mod save and observe activation.
- Mid-flow reload: save while the objective is active, reload, and record whether the intended node resumes.
- Completed reload: save after completion, reload, and verify no reactivation.
- Reinstall same build: prove that reinstalling does not change the result already stored in the save.
- Clean replay: return to the original pre-mod save for another first-run pass.
Record which save was used with the archive filename and test result. “Works on my save” is not enough evidence to reproduce a lifecycle claim.
Root and child questphases
A root questphase is registered beneath the game’s quest root. An external child questphase is a separate archived resource invoked by a node in another phase. They use the same CR2W resource type, but registration, ownership, and interfaces differ.
Root registration
ArchiveXL attaches a mod-owned root:
quest:
phases:
- path: mod\cqa\cqa001\phases\cqa001.questphase
parent: base\quest\cyberpunk2077.quest
The registration makes the resource reachable from the game’s root quest structure. It does not create journal or localization entries; those remain separate registrations.
Register only the root phase. An external child must be present at its depot
path in the packed archive, but it is reached through the parent node’s
phaseResource; it is not a second ArchiveXL root registration. Registering
every child beneath base\quest\cyberpunk2077.quest changes the composition
instead of satisfying the reference.
Lab 1 is a single root phase. Its input reaches the one-shot guard without a location gate, so it can activate independently of player position. Lab 4 keeps one registered root and resolves its boundary activity as an external child.
Child-phase interface
A parent uses questPhaseNodeDefinition to refer to another questphase through
its soft phaseResource. Lab 4 uses this returning-child interface:
parent questPhaseNodeDefinition
phaseResource: mod\cqa\...\child.questphase
input socket: In1
output socket: Out1
child questphase
questInputNodeDefinition.socketName: In1
questOutputNodeDefinition.socketName: Out1
Socket names form the contract for this arrangement. The parent’s In1 maps
to the child’s exposed input, and the child’s Out1 maps back to the parent
node’s output.
The path and the socket contract solve different problems. A returning child
cannot hand control back through Out1 when that socket is absent, misspelled,
or unconnected. Matching sockets cannot rescue a missing archived child
resource. This is not a rule that every external phase needs an output:
Observed in vanilla, the focused sts_wat_nid_03 root also contains an
input-only external phase node. Its absence of Out1 is a different lifecycle,
not an inherently defective resource.
Named outputs can represent distinct results, especially for scenes and later
branching phases. An output named accepted is not interchangeable with
failed merely because both terminate the child.
Why create a child phase?
Use a child when it creates a meaningful ownership boundary:
- a reusable or independently testable activity;
- a lifecycle with its own setup and cleanup;
- a graph large enough to obscure the parent orchestration;
- a resource that owns specific world-prefab dependencies;
- a stage with named outcomes the parent must consume.
Do not create a child merely to hide one unexplained node. The book explains the child’s complete graph and every required external resource.
Parent and child responsibilities
A useful split looks like:
root
-> decide whether the quest should run
-> activate top-level journal state
-> run child activity
-> consume child outcome
-> complete or choose the next child
child activity
-> acquire or activate its dependencies
-> wait for readiness
-> perform the focused work
-> clean up what it owns
-> emit a named result
The parent should not assume that starting a child automatically creates its community, scene, journal entries, or world objects. The child can coordinate those resources only after they have been authored and registered.
Registration here means registration appropriate to the resource: the quest
root, journal tree, localization tables, and streaming block have their own
merge points. An external child questphase itself is archived and referenced
by phaseResource, not independently merged into the game quest root.
Inline and external phases
A phase node can point to an external phaseResource; CR2W also has structures
for inline/inplace phase graphs. This book starts with external mod-owned
resources because their depot paths, ownership, downloads, and round-trip
inspection are explicit.
Do not generalize an inline vanilla shape into a reader template until its inheritance, prefab, and save behavior have been isolated.
Prefab dependencies
World-aware phases can declare quest-prefab dependencies:
- root
phasePrefabsdeclares prefab roots owned by that phase composition; the usable scope can include external children invoked beneath the root; - a phase node has a separate, node-local
phaseInstancePrefabsarray. Its presence is not evidence that a tool must duplicate the root declaration there.
Observed in vanilla:
base\open_world\street_stories\watson\northside_industrial_district\sts_wat_nid_03\phases\sts_wat_nid_03.questphase
declares a prefab while its external
sts_wat_nid_03_openworld.questphase child has an empty declaration and uses
references beneath that prefab. The observation supports root-owned scope; it
does not replace a mod-owned runtime test.
Lab 4 declares #cqa004_pr_handoff on the root, leaves the child’s prefab list
empty, and keeps phaseInstancePrefabs empty on the invoking node. Test
NodeRef resolution while the child is active, then after streaming and reload.
Do not turn an authoring tool’s convenience switch into an engine rule.
Research tooling may expose an inherit_phase_prefabs policy that copies or
propagates declarations while producing test resources. That remains tool
policy, not proof that native CR2W requires the copy. Inspect the emitted
phasePrefabs, phaseInstancePrefabs, and phaseResource values and validate
the resulting game-resource arrangement.
Lab 1 has empty prefab arrays because it contains no NodeRef. Lab 3 keeps its reach/leave world activity in one root. Lab 4 moves that activity pattern into an external child so the root-owned prefab scope and handoff are visible in a small graph.
Completion handoff
Completing the last internal action does not automatically advance the parent. The child must reach its output, and the parent node’s matching output must have an outgoing connection.
Review both sides:
- Does every intended child route reach an output?
- Do the output names match the parent phase node?
- Does every parent output lead somewhere intentional?
- Do cut or failure routes clean up before handoff?
An orphaned Out1 is a graph defect even when the child looks complete in
isolation.
CutDestination sockets are structurally present on the focused external
phase-node shape, but that does not establish their runtime interruption
semantics. Lab 4 deliberately leaves them unconnected and labels cut behavior
Experimental rather than inventing a safe cleanup recipe.
The Questphases section continues with the exact resource anatomy, interface properties, external-child fields, prefab dependencies, and interruption review. Lab 4: Handoff Point applies the model to downloadable start and completed checkpoints.
Lifecycle and cleanup
Quest authoring is lifecycle design. The happy path is only one transition through a system that can be saved, reloaded, interrupted, re-entered, or installed over older state.
A complete activity lifecycle
A focused activity usually has these responsibilities:
activation
-> dependency readiness
-> player-facing work
-> outcome capture
-> presentation completion
-> cleanup
-> handoff or termination
The owner of an activated dependency should also define when it is released. Examples include:
- deactivate a community after the player leaves a safe boundary;
- hide or inactivate a mappin when its objective ends;
- stop a parallel failure monitor after success;
- return a named scene outcome before terminating its child phase;
- leave persistent facts intact when they are the intended re-entry guard.
Cleanup is not synonymous with resetting everything. Completion facts and journal history may intentionally persist while transient actors, markers, and monitors stop.
Completion ordering
Ordering is observable:
succeed objective
-> set completion fact
-> succeed quest
-> terminate
Moving the fact earlier may prevent recovery from a partial run. Moving it after termination may make it unreachable. Setting the quest succeeded before its objective can produce confusing presentation.
There is no universal ordering for every quest. State the intended invariant, then test interruption at the boundaries that matter.
Re-entry policy
Every root or repeatable activity needs an explicit policy:
| Policy | Required control |
|---|---|
| One-shot | Persistent completion guard and a terminating already-done route |
| Resume | Save-safe stage state and a route back into the correct activity |
| Retry after failure | Failure cleanup plus a deliberate reset boundary |
| Repeatable | State reset, cooldown or activation gate, and protection from overlapping runs |
“The graph starts at its input” does not answer what should happen when saved state says some of its work is already active.
Cut and interruption
A cut path is a different outcome, not a shortcut to the normal final node. Before wiring it, decide:
- which scenes or child phases receive interruption;
- which objectives fail, become inactive, or remain resumable;
- which communities, devices, or markers remain valid;
- which facts distinguish interruption from success;
- whether the parent terminates, retries, or chooses another branch.
Complex interruption is introduced only after the normal lifecycle is runtime-proven. A guessed cleanup route can be more destructive than leaving an experimental socket unwired.
The validation ladder
Each check proves a different claim:
| Check | What it proves | What it does not prove |
|---|---|---|
| WolvenKit save or CR2W deserialization | The editor/converter accepted the serialized structure | Runtime references and behavior |
| CR2W round trip | Important structure survives binary serialization | That the game can execute it |
| Handle and graph validation | References, nodes, sockets, and edges are internally consistent | External resource existence |
| Archive packing and path listing | Intended CR2W payloads entered the archive at known depot paths | Loose framework files were installed |
| ArchiveXL log registration | The framework saw and registered configured resources | Player-facing behavior |
| Focused runtime route | The tested behavior occurred in one environment and save state | Reload, interruption, or replay safety |
| Clean-save acceptance matrix | The documented lifecycle cases pass from controlled state | Compatibility with untested versions |
“WolvenKit saved it” and “the archive packed” are therefore intermediate evidence, not completion.
Foundation checkpoint
Before proceeding to a lab, be able to explain:
- which resource owns every referenced object or string;
- how execution enters and leaves every node;
- the type and domain of every identifier;
- which state can already exist in the save;
- who owns cleanup and termination;
- how you will test completion, interruption, and reload.
If one answer is “the template handles it,” the resource has not yet been explained.
Lab 1: First Signal
Lab 1 establishes the smallest useful native quest loop: register a root questphase, show one objective, wait ten real-time seconds, complete the objective and quest, persist a one-shot guard, and terminate.
| Record | Value |
|---|---|
| Structural validation date | 2026-07-27 |
| Runtime test date | Not yet recorded |
The supplied resources pass WolvenKit 8.19.0 deserialization and round-trip checks. Follow the test chapter to verify activation, timing, journal state, reload, reinstall, and clean replay in your game.
The complete manual WolvenKit authoring, installation, test, and reset walkthrough builds this design from the empty checkpoint. This page remains the concise resource and graph reference.
Outcome
On a clean save, the intended player-facing sequence is:
root quest starts
-> already completed?
+-> yes: terminate
+-> no: show First Signal and Wait for the signal.
-> wait 10 real-time seconds
-> complete objective
-> persist cqa001_completed = 1
-> complete quest
-> terminate
There is no trigger, marker, scene, character, device, combat encounter, or custom world resource. Those systems enter in later labs.
Prerequisites and tested baseline
Read Foundations first. The lab assumes you can distinguish resource ownership, socket semantics, identifier domains, and save-backed state.
Use the exact pinned version set:
Cyberpunk 2077 2.31a, WolvenKit 8.19.0, ArchiveXL 1.27.0, RED4ext
1.30.0, and the pinned redscript 0.5.31 (ArchiveXL requires 0.5.31 or
newer).
The example has two checkpoints:
- Download the start checkpoint, an empty WolvenKit project;
- Download the completed checkpoint, containing the mod-owned resource set and serialized review artifacts.
The source directories remain available on GitHub.
Use a dedicated test save. The completion fact is stored in the save and is deliberately not reset by the quest.
Follow the manual authoring walkthrough to create the resources, enter the property paths, wire every socket, install the project, and run the Experimental clean-save acceptance matrix.
Resource model
Three native resources and one framework file divide responsibility:
| Resource | Depot path | Owns |
|---|---|---|
| Root questphase | mod\cqa\cqa001\phases\cqa001.questphase | Execution order, one-shot guard, delay, journal state changes, completion fact, and termination |
| Journal | mod\cqa\cqa001\journal\cqa001.journal | Quest, phase, and objective entries |
| Onscreen localization | mod\cqa\cqa001\localization\en-us\onscreens\cqa001.json | Player-facing English strings referenced by journal entries |
| ArchiveXL registration | CQA_Lab01_OneShot.archive.xl | Attaches the root questphase and merges the journal and localization resources |
The questphase does not contain the text “First Signal.” It changes journal entry state. The journal contains localization keys, and the onscreen localization resource resolves those keys to text.
Journal tree
The journal resource supplies this exact tree:
quests
└── minor_quest
└── cqa001 gameJournalQuest
└── cqa001_01 gameJournalQuestPhase
└── cqa001_01_obj_wait gameJournalQuestObjective
Localization keys:
| Key | English text |
|---|---|
cqa_cqa001_title | First Signal |
cqa_cqa001_objective_wait | Wait for the signal. |
Questphase journal paths use fileEntryIndex: 2, the zero-based path component
for the containing cqa001 file entry in quests/minor_quest/cqa001/....
This value is not the leaf index and is not a CR2W handle.
Exact questphase
The figure is generated from the supplied WolvenKit CR2W-JSON. Match its node IDs, socket names, and connections to your graph; editor layout can differ.
| ID | RED node type | Incoming socket | Decisive property | Purpose |
|---|---|---|---|---|
0 | questInputNodeDefinition | root In1 | socket name In1 | Entry from the registered root |
10 | questConditionNodeDefinition | In | cqa001_completed Equal 0 | Evaluate once and choose True or False immediately |
11 | questJournalNodeDefinition | Active | path quests/minor_quest/cqa001 | Activate and track the quest |
12 | questJournalNodeDefinition | Active | objective path | Activate and track the objective |
13 | questPauseConditionNodeDefinition | In | questRealtimeDelay_ConditionType, 10 seconds | Hold this execution path until elapsed real time reaches ten seconds |
14 | questJournalNodeDefinition | Succeeded | objective path | Mark the objective succeeded |
15 | questFactsDBManagerNodeDefinition | In | set cqa001_completed exactly to 1 | Persist one-shot completion state |
16 | questJournalNodeDefinition | Succeeded | path quests/minor_quest/cqa001 | Mark the quest succeeded |
1 | questOutputNodeDefinition | In | Terminating | End either the already-completed or first-run route |
Condition and PauseCondition are not interchangeable. ID 10 snapshots a
fact and immediately chooses one output. ID 13 keeps waiting until its time
condition becomes true.
The completion fact is set after the objective succeeds but before the quest succeeds. If testing later exposes a save interruption between those nodes, the ordering must be revisited and recorded rather than silently called safe.
Registration
ArchiveXL registers the root and resource merges:
quest:
phases:
- path: mod\cqa\cqa001\phases\cqa001.questphase
parent: base\quest\cyberpunk2077.quest
journal:
- mod\cqa\cqa001\journal\cqa001.journal
localization:
onscreens:
en-us:
- mod\cqa\cqa001\localization\en-us\onscreens\cqa001.json
The parent path is a vanilla depot reference. The example cites it; it does
not redistribute the base-game resource.
See Registering a root questphase for the difference between root reachability, location gates, resource merges, and runtime proof.
Verification plan
Structural checks already completed:
The supplied resources have been round-tripped with WolvenKit 8.19.0.
Compare the node IDs, sockets, connections, journal tree, and localization keys
with your authored project.
Runtime acceptance still required:
- start from a save that has never loaded
cqa001; - confirm the quest and objective activate once;
- confirm completion occurs after ten seconds of real time;
- confirm the objective and quest both show succeeded presentation;
- save, reload, and confirm the quest does not reactivate;
- inspect ArchiveXL and game logs for registration or lookup errors.
Until those checks pass, expected behavior is not runtime proof.
Common failure boundaries
- If nothing starts, inspect ArchiveXL root-phase registration and the depot path before changing graph logic.
- If text is blank, compare journal localization keys with onscreen secondary keys. Journal IDs are not localization IDs.
- If the objective remains active, verify that the edge enters ID
14throughSucceeded, notActive. - If the quest repeats, check
cqa001_completedon the exact save used for the second run. - If a test appears fixed after editing a device, journal, scene, or fact, repeat it on a clean save before attributing the change to the resource edit.
Lab sequence: All labs · Next: Author First Signal in WolvenKit.
Lab 1: author First Signal
This walkthrough builds the complete First Signal checkpoint from the empty Lab 1 WolvenKit project. You will create three mod-owned CR2W resources, wire the quest graph, add the ArchiveXL registration, install the project, and run a save-aware acceptance pass.
| Record | Value |
|---|---|
| Runtime test date | Not yet recorded |
The supplied checkpoint passes WolvenKit 8.19.0 structural checks. Follow the test procedure after authoring so you can compare the expected route with what actually happens in your game.
Use this exact baseline:
- Cyberpunk 2077
2.31a; - WolvenKit
8.19.0; - ArchiveXL
1.27.0; - RED4ext
1.30.0; - redscript
0.5.31(ArchiveXL 1.27.0 requires0.5.31or newer).
Complete the pinned toolchain setup, review the project and resource structure, and read Lab 1’s design before starting. Keep the completed checkpoint extracted in a separate directory for comparison.
Editor boundary used in this guide
WolvenKit 8.19.0 exposes these named commands in the tested editor:
- File > New File for an empty CR2W or ArchiveXL file;
- Add Node on an empty part of a quest graph;
- Show Unused Sockets on a graph node;
- Node Properties beside the quest graph;
- Project > Scan for broken file references, Scan for broken files, and Run File Validation on the entire project;
- Build > Install.
The nested CR2W type chooser is contextual: its placement and visible rows
depend on the selected property. Where that makes a click label ambiguous,
this guide gives a RED property path instead. At that path, use the plus
control whose tooltip is Add Handle for a null handle or Add New Element
for an array, then choose the exact RED type named here. This is still an
in-editor workflow. Do not edit the serialized files under source\raw.
1. Open the start checkpoint
-
Extract the start checkpoint to a writable working directory outside the game directory.
-
In WolvenKit, choose File > Open Project and open
CQA_Lab01_OneShot_Start.cpmodproj. -
Save a separate backup of that freshly extracted directory. WolvenKit’s quest editor has no general undo/redo safety net for this graph workflow.
-
In the Project Explorer, open the
source\archivedirectory in Windows Explorer and create this directory tree:mod\cqa\cqa001\ ├── journal\ ├── localization\en-us\onscreens\ └── phases\
The start checkpoint’s project ModName ends in _Start, so its packed
archive will be named CQA_Lab01_OneShot_Start.archive. That external archive
name can differ from the completed checkpoint’s archive name; the depot paths
inside it must match exactly.
2. Create the three CR2W resources
Create each file through File > New File. Choose the CR2W Files category, select the listed root type, replace the proposed name with the relative path below, and select Create.
| Root type shown in New File | Name | Result beneath source\archive |
|---|---|---|
questQuestPhaseResource | mod\cqa\cqa001\phases\cqa001.questphase | Root questphase |
gameJournalResource | mod\cqa\cqa001\journal\cqa001.journal | Journal tree |
JsonResource with the .json description | mod\cqa\cqa001\localization\en-us\onscreens\cqa001.json | Onscreen localization |
The last file has a .json depot extension but is a binary CR2W
JsonResource. It is not the plain-text review file whose name ends in
.json.json in the completed checkpoint.
Press Ctrl+S after creating each file. The empty questphase should already
contain a non-null questGraphDefinition; this is the scratch-file behavior
of WolvenKit 8.19.0.
3. Author the journal tree
Open cqa001.journal. Work in the CR2W property tree and build this handle
chain:
RootChunk gameJournalResource
└── entry gameJournalRootFolderEntry
└── entries[0] gameJournalPrimaryFolderEntry
└── entries[0] gameJournalFolderEntry
└── entries[0] gameJournalQuest
└── entries[0] gameJournalQuestPhase
└── entries[0] gameJournalQuestObjective
At RootChunk.entry, add a handle of type
gameJournalRootFolderEntry. At every following entries array, add one
element and select the type shown above. Do not add an extra wrapper folder.
Set these properties. Paths use RED property names and array indexes, not
CR2W-JSON HandleId wrappers.
Property path below RootChunk | Value |
|---|---|
cookingPlatform | PLATFORM_PC |
entry.descriptor.DepotPath | base\journal\descriptor.journaldesc |
entry.descriptor.Flags | Soft |
entry.entries[0].id | quests |
entry.entries[0].entries[0].id | minor_quest |
entry.entries[0].entries[0].entries[0].id | cqa001 |
...cqa001.title.unk1 | 0 |
...cqa001.title.value | cqa_cqa001_title |
...cqa001.type | MinorQuest |
...cqa001.entries[0].id | cqa001_01 |
...cqa001_01.entries[0].id | cqa001_01_obj_wait |
...cqa001_01_obj_wait.description.unk1 | 0 |
...cqa001_01_obj_wait.description.value | cqa_cqa001_objective_wait |
...cqa001_01_obj_wait.counter | 0 |
...cqa001_01_obj_wait.optional | 0 |
In the abbreviated paths, ...cqa001 means
entry.entries[0].entries[0].entries[0]; each later name identifies its one
child entry.
base\journal\descriptor.journaldesc is the game-owned journal descriptor
that supplies the standard hierarchy contract. It is a reference only: do not
extract it into, rename it for, or redistribute it with the tutorial project.
Leave every entry’s journalEntryOverrideDataList empty. Leave the quest’s
districtID empty and recommendedLevelID at zero. Leave the phase’s
locationPrefabRef at zero. Leave the objective’s districtID empty and its
itemID and locationPrefabRef at zero. These values are deliberate: Lab 1
has no district, item, marker, or world reference.
Press Ctrl+S, close the tab, and reopen it. Confirm that the six-entry chain
and both localization keys survived serialization.
4. Author the onscreen localization
Open the CR2W cqa001.json under source\archive. At RootChunk.root, add a
handle of type localizationPersistenceOnScreenEntries. Add two elements to
its entries array; both elements are
localizationPersistenceOnScreenEntry.
Set the resource and entries exactly:
Property path below RootChunk | Value |
|---|---|
cookingPlatform | PLATFORM_PC |
root.entries[0].primaryKey | 0 |
root.entries[0].secondaryKey | cqa_cqa001_title |
root.entries[0].femaleVariant | First Signal |
root.entries[0].maleVariant | empty string |
root.entries[1].primaryKey | 0 |
root.entries[1].secondaryKey | cqa_cqa001_objective_wait |
root.entries[1].femaleVariant | Wait for the signal. |
root.entries[1].maleVariant | empty string |
This reproduces the structurally validated checkpoint shape. Runtime fallback and presentation for this new arrangement remain part of the Lab 1 Experimental acceptance run.
Save, close, and reopen the resource. Confirm the secondary keys exactly match
the journal’s title.value and description.value strings.
5. Add the quest nodes
Open cqa001.questphase. Its Phase Resources view should show empty
inplacePhases and phasePrefabs arrays. Leave both empty.
On an empty area of the graph, right-click and choose Add Node, then add the following nodes in order. The menu uses the short editor label; the second column records the serialized RED type.
| Add Node label | RED type | Target graph ID |
|---|---|---|
Input | questInputNodeDefinition | 0 |
Output | questOutputNodeDefinition | 1 |
Condition | questConditionNodeDefinition | 10 |
Journal | questJournalNodeDefinition | 11 |
Journal | questJournalNodeDefinition | 12 |
PauseCondition | questPauseConditionNodeDefinition | 13 |
Journal | questJournalNodeDefinition | 14 |
FactsDBManager | questFactsDBManagerNodeDefinition | 15 |
Journal | questJournalNodeDefinition | 16 |
WolvenKit assigns fresh IDs as nodes are created. Select each node and use
Node Properties to set its id to the target value. If you created all
nine first, change them in the table’s order: moving the initial input from
1 to 0 frees 1 for the output, and the remaining IDs move to the unused
10–16 range.
Set the interface fields:
| Node | Property | Value |
|---|---|---|
ID 0 | socketName | In1 |
ID 1 | socketName | Out1 |
ID 1 | type | Terminating |
socketName is the phase interface. It is different from the input node’s
internal Out socket and the output node’s internal In socket.
Press Ctrl+S, close the questphase tab, and reopen it now. WolvenKit’s canvas
can retain the wrappers’ original display IDs after an in-place id edit.
Before following the ID-addressed steps below, verify that the graph now shows
exactly [0], [1], and [10] through [16]. Reopening also rebinds the
connector owners to the serialized IDs.
6. Configure the node payloads
ID 10: one-shot guard
At the node’s condition property, add a questFactsDBCondition handle. At
condition.type, add a questVarComparison_ConditionType handle, then set:
| Property path | Value |
|---|---|
condition.type.factName | cqa001_completed |
condition.type.comparisonType | Equal |
condition.type.value | 0 |
This node evaluates once on entry. It does not wait for the fact to change.
IDs 11, 12, 14, and 16: journal operations
For each journal node, add a questJournalQuestEntry_NodeType handle at
type. Then select type.path, choose Add Handle, and create a
gameJournalPath handle. New journal nodes do not create that nested path for
you. Set the common fields on all four nodes:
| Property path | Value |
|---|---|
type.optional | 0 |
type.sendNotification | 1 |
type.trackQuest | 1 |
type.version | Initial |
type.path.editorPath | empty string |
type.path.fileEntryIndex | 2 |
Then set each typed path:
| ID | type.path.className | type.path.realPath |
|---|---|---|
11 | gameJournalQuest | quests/minor_quest/cqa001 |
12 | gameJournalQuestObjective | quests/minor_quest/cqa001/cqa001_01/cqa001_01_obj_wait |
14 | gameJournalQuestObjective | quests/minor_quest/cqa001/cqa001_01/cqa001_01_obj_wait |
16 | gameJournalQuest | quests/minor_quest/cqa001 |
IDs 12 and 14 address the same entry. Their incoming sockets will request
different states.
ID 13: ten-second realtime gate
At condition, add a questTimeCondition handle. At condition.type, add a
questRealtimeDelay_ConditionType handle and set:
| Property path | Value |
|---|---|
condition.type.hours | 0 |
condition.type.minutes | 0 |
condition.type.seconds | 10 |
condition.type.miliseconds | 0 |
miliseconds is the RED property spelling. This pause condition holds its
path until the realtime delay becomes true.
ID 15: persistent fact write
WolvenKit initializes a new FactsDBManager node with a
questSetVar_NodeType. Verify and set:
| Property path | Value |
|---|---|
type.factName | cqa001_completed |
type.setExactValue | 1 |
type.value | 1 |
This writes exactly 1; it does not increment the existing value.
7. Expose and connect every socket
Right-click each node and choose Show Unused Sockets. Drag from each source output to the destination input in this table:
| Source | Destination | Meaning |
|---|---|---|
0.Out | 10.In | Enter the one-shot guard |
10.True | 11.Active | First run: activate and track the quest |
10.False | 1.In | Already complete: terminate immediately |
11.Out | 12.Active | Activate and track the objective |
12.Out | 13.In | Begin the realtime wait |
13.Out | 14.Succeeded | Succeed the objective after the wait |
14.Out | 15.In | Continue to the completion fact write |
15.Out | 16.Succeeded | Succeed the quest after the fact write |
16.Out | 1.In | Terminate the successful route |
Socket inventory matters even when a socket is unused:
| Node IDs | Supplied sockets | Lab 1 use |
|---|---|---|
0 | CutDestination, Out | Out is connected; cut is unwired |
1 | CutDestination, In | both normal routes enter In; cut is unwired |
10 | CutDestination, In, False, True | all except cut are connected |
11, 12, 14, 16 | CutDestination, Active, Inactive, Succeeded, Failed, Out | the connected state input differs by node; Out continues; all other state inputs and cut are unwired |
13 | CutDestination, In, Out | normal wait path is connected; cut is unwired |
15 | CutDestination, In, Out | normal fact-write path is connected; cut is unwired |
For ID 11, Active is connected. For ID 12, Active is connected. For
IDs 14 and 16, Succeeded is connected. An edge entering a journal node’s
ordinary-looking but wrong state socket changes the requested journal state.
The unused CutDestination sockets are visible lifecycle obligations, not
missing normal-flow edges. Lab 1 does not claim cut-safe behavior. Do not wire
them to the terminating output as a guess.
Press Ctrl+S, close the questphase, and reopen it. Compare the graph against
the exact generated figure. Node placement may
differ; IDs, types, sockets, payloads, and edges must not.
8. Register the resources with ArchiveXL
Choose File > New File, select the ArchiveXL category and
ArchiveXL file, name it CQA_Lab01_OneShot.archive.xl, and select
Create. WolvenKit places it under source\resources.
Enter this YAML exactly:
quest:
phases:
- path: mod\cqa\cqa001\phases\cqa001.questphase
parent: base\quest\cyberpunk2077.quest
journal:
- mod\cqa\cqa001\journal\cqa001.journal
localization:
onscreens:
en-us:
- mod\cqa\cqa001\localization\en-us\onscreens\cqa001.json
Save the file. The three entries have separate jobs: attach the executable
phase beneath the vanilla quest root, merge the journal, and register the
English onscreen strings. The parent path is a reference; do not extract or
add base\quest\cyberpunk2077.quest to the project.
Your authored source tree should now contain:
source\
├── archive\mod\cqa\cqa001\
│ ├── journal\cqa001.journal
│ ├── localization\en-us\onscreens\cqa001.json
│ └── phases\cqa001.questphase
└── resources\CQA_Lab01_OneShot.archive.xl
9. Compare with the completed checkpoint
Open the completed .cpmodproj in a second WolvenKit instance, or close the
start project and open the completed project after saving. Compare semantic
structure, not binary bytes or CR2W handle numbers.
Check all of the following:
- the three depot paths and ArchiveXL filename match;
- the journal has the six exact types and IDs in one chain;
- both journal localization keys resolve to the two secondary keys;
- the questphase has IDs
0,1, and10–16, with no extra nodes; - the nine connections match the socket table above;
- both outer questphase arrays are empty;
- the condition, delay, fact-write, journal path, notification, and tracking values match the property tables;
- the ArchiveXL YAML matches by registration meaning and depot path.
If you need a text diff, Project Explorer can export a mod-owned CR2W file to
JSON under the mirrored source\raw path. Use that export only as a review
artifact. Do not repair the lab by editing raw JSON, and do not compare
HandleId allocation as if it were graph identity.
10. Validate, install, and inspect the install
With all tabs saved:
- run Project > Scan for broken file references;
- run Project > Scan for broken files;
- run Project > Run File Validation on the entire project;
- resolve every validation error owned by this project before continuing;
- close Cyberpunk 2077;
- choose Build > Install and wait for packing and installation to finish;
- read WolvenKit’s Log panel rather than treating the button press as proof.
Use the normal Install command for this ArchiveXL project. Do not choose Install as REDmod or a hot-reload command. The full install, launch, log, and test procedure is collected in Install, test, and reset.
The vanilla journal descriptor and root parent are deliberate references;
they need not be copied into the project. Treat any other unresolved reference
or validation error as a stop condition, even if it does not spell out one of
the three mod depot paths.
For the start checkpoint, verify these exact installed files beneath the game directory:
archive\pc\mod\CQA_Lab01_OneShot_Start.archive
archive\pc\mod\CQA_Lab01_OneShot.archive.xl
If you install the completed checkpoint instead, the archive is named
CQA_Lab01_OneShot.archive. The loose .archive.xl must be present beside the
archive in either case. A packed archive alone cannot register the root,
journal, or localization resources.
After launching the game once, inspect:
red4ext\plugins\ArchiveXL\ArchiveXL.log
red4ext\logs\red4ext.log
red4ext\logs\game.log
r6\logs\redscript_rCURRENT.log
Search the ArchiveXL log for CQA_Lab01_OneShot.archive.xl, cqa001, or an
error naming one of the registered depot paths. Confirm that RED4ext loaded
ArchiveXL and that redscript did not report a new dependency/load error.
Registration without errors is evidence about registration, not proof that
the quest route executed.
11. Test and reset
Continue with Install, test, and reset. Use the archive
built from your start project, CQA_Lab01_OneShot_Start.archive, and the shared
CQA_Lab01_OneShot.archive.xl registration file.
That guide covers the first load, mid-flow reload, completed reload,
reinstallation, clean replay, logs, and removal. Begin from a manual save made
before any version of cqa001 was installed. Changing its completion fact alone
does not reset the journal or saved graph state.
The intended first run activates First Signal and Wait for the signal., then succeeds after about ten real-time seconds. This behavior remains Experimental until tested. The terminating output has no separate visible indicator; infer it from the graph, successful journal state, and absence of reactivation.
To compare your build with the supplied completed checkpoint, close the game,
remove the _Start archive, install the completed project, and repeat from the
same untouched baseline. Keep only one checkpoint’s archive installed.
Failure routing
| Symptom | Inspect first |
|---|---|
| No quest activation | Installed .archive.xl, its quest.phases path, ArchiveXL log, and the save’s completion fact |
| Quest title or objective text is blank | Journal localization key versus onscreen secondaryKey |
| Objective stays active | ID 13.Out to ID 14.Succeeded, not 14.Active |
| Quest finishes but repeats | ID 15 exact fact write and the fact value on the reloaded save |
| First run works; replay looks inconsistent | Journal state and other save-backed data left by the earlier run |
| WolvenKit graph looks different after reopen | Compare IDs, node types, sockets, and edges; ignore layout and handle allocation |
Serialization, validation, packing, ArchiveXL registration, and runtime behavior are different evidence steps. Keep the Experimental label until the clean-save matrix—not merely the successful build—has been retained.
Lab sequence: All labs · Previous: Lab 1: First Signal · Next: Install, test, and reset.
Install, test, and reset
Install Lab 1, check that First Signal appears and completes, then test reload and replay from separate saves. This guide works with either your authored start project or the supplied completed checkpoint.
| Record | Value |
|---|---|
| Target environment | Cyberpunk 2077 2.31a, WolvenKit 8.19.0, ArchiveXL 1.27.0, RED4ext 1.30.0, redscript 0.5.31 |
| Supplied resource status | Structurally validated |
| In-game result | Record it while following the save matrix below |
| Runtime test date | Not yet recorded |
The expected results below remain Experimental until observed in game. Keep notes of what actually happens, including failures or cases you cannot run.
Prepare an untouched save
The completion fact, journal state, and active graph state live in the save.
Create or identify a manual save made before any version of cqa001 was
installed or registered. Keep that save as an immutable baseline and make new
save slots for test cases.
A useful label in your test notes is:
cqa001-clean-baseline — manual save created before first cqa001 install
Removing the mod after it has run does not erase state from a save. Setting
cqa001_completed back to 0 with an optional console may help diagnose one
branch, but it does not clear journal entries, quest checkpoints, scene state,
communities, or devices. A fact edit is therefore never a clean-save result,
and no console mod is required by this guide.
See Facts, journals, and saves for the state model behind this rule.
Install the completed checkpoint
If you just installed your authored start project, keep that installation and
continue with the file and log checks below. Its archive is
CQA_Lab01_OneShot_Start.archive; the loose registration file is still
CQA_Lab01_OneShot.archive.xl. Use the _Start archive name wherever this
guide names the completed archive, including removal.
To test the supplied completed project instead:
- Exit the game completely.
- Download and extract the completed Lab 1 checkpoint into a writable projects directory outside the game.
- Open
CQA_Lab01_OneShot.cpmodprojin WolvenKit. - Confirm the project contains the three cooked resources under
source\archive\mod\cqa\cqa001andsource\resources\CQA_Lab01_OneShot.archive.xl. - Use the normal Install action. Do not choose Install as REDmod and do not use hot reload for this acceptance run.
- Read WolvenKit’s Log panel. Stop if packing reports an error or an expected depot path is missing.
WolvenKit documents normal Install as “pack the project, then copy the
generated packed hierarchy into the game directory” in its
menu reference.
Verify these installed files exist:
<Cyberpunk 2077>\archive\pc\mod\CQA_Lab01_OneShot.archive
<Cyberpunk 2077>\archive\pc\mod\CQA_Lab01_OneShot.archive.xl
Install only one checkpoint at a time. Before switching, close the game and remove the other checkpoint’s archive so it cannot supply the same depot paths.
Check startup and registration logs
Launch the game and reach the point where a save can be loaded. After each test, preserve the relevant portions of:
<Cyberpunk 2077>\red4ext\logs\red4ext.log
<Cyberpunk 2077>\red4ext\logs\game.log
<Cyberpunk 2077>\red4ext\plugins\ArchiveXL\ArchiveXL.log
<Cyberpunk 2077>\r6\logs\redscript_rCURRENT.log
Check that RED4ext loaded ArchiveXL, redscript compiled without errors, and
ArchiveXL did not report a failure resolving CQA_Lab01_OneShot.archive.xl or
these depot paths:
mod\cqa\cqa001\phases\cqa001.questphase
mod\cqa\cqa001\journal\cqa001.journal
mod\cqa\cqa001\localization\en-us\onscreens\cqa001.json
Do not require a guessed fixed “success” sentence: log wording can change. Record the relevant actual lines. An ArchiveXL registration without errors is framework evidence, not proof that the graph ran or the journal rendered.
Run the save matrix
Use separate manual save slots for each state. Keep the installed build unchanged.
| Case | Starting state | Expected observation | Required record |
|---|---|---|---|
| First load | Untouched pre-mod save | Quest and objective activate once; after about ten real-time seconds the objective and quest succeed | Start save identity, visible state/timing, logs |
| Mid-flow reload | New save made while the objective is active | Reload reaches a defined, non-duplicated state rather than silently creating a second run | Save identity, state before/after reload, logs |
| Completed reload | New save made after success | Root guard takes the already-complete route; no reactivation | Completion state before exit and after reload |
| Reinstall same build | Completed save; the same archive and .archive.xl reinstalled | Reinstalling bytes does not erase save-backed completion or reactivate the quest | Installed filenames and observed state |
| Clean replay | Original untouched pre-mod save | The same first-run route remains reproducible | Original save identity and observations |
The player-facing oracle for the first and clean-replay cases is:
First Signal
-> objective: Wait for the signal.
-> approximately 10 real-time seconds
-> objective succeeded
-> quest succeeded
-> cqa001 does not activate again on the completed save
“Approximately” allows UI presentation and load scheduling; the authored pause value is ten seconds. Record the measured observation rather than rounding it into a claimed exact runtime duration.
If you cannot create a save during the short active window, record the mid-flow case as pending. Do not convert an unrun case into a pass and do not change the delay merely to make this test easier to run.
Reset safely between runs
There are three different resets:
- Project-output reset: WolvenKit’s Clean All removes the generated
project
packeddirectory. It does not remove installed game files, source resources, or save state. - Installation reset: with the game closed, remove only
CQA_Lab01_OneShot.archiveandCQA_Lab01_OneShot.archive.xlfrom<Cyberpunk 2077>\archive\pc\mod. Preserve them with the test record first if you need to reproduce a problem. Do not delete the wholemoddirectory. - State reset: load the preserved manual save that predates the first
cqa001installation. Reinstall the intended build before the clean replay.
For a controlled rebuild, close the game, clean project output, install once, then load the untouched baseline. Changing source files, framework versions, installed bytes, and starting saves at the same time makes the result impossible to attribute.
Diagnose by layer
| Symptom | First boundary to check |
|---|---|
| WolvenKit cannot pack | Project source paths and WolvenKit Log panel |
.archive exists but .archive.xl does not | source\resources staging |
| ArchiveXL log is absent | RED4ext/plugin installation |
| Registration reports a missing phase | Exact archive depot path versus .archive.xl path |
| Quest appears without text | Journal/localization merge and localization keys |
| Quest works only on one old save | Persistent fact, journal, or checkpoint state |
| Completed quest runs again | Guard fact write, save identity, or duplicate installed builds |
Stop after the first failing boundary. Repacking repeatedly cannot fix a missing framework, and editing the graph cannot clean an old save.
Lab sequence: All labs · Previous: Author First Signal in WolvenKit · Next lab: Lab 2: Signal Race.
Questphases
A questphase is an executable quest graph stored as a
questQuestPhaseResource. It coordinates progression; it does not replace the
resources that own journal content, localized text, scenes, communities, or
world objects.
This section turns the root-and-child model into exact native resource contracts:
- Resource anatomy identifies the CR2W root, graph, nodes, sockets, external references, and dependency lists.
- Registering a root separates ArchiveXL root attachment from normal depot-resource resolution.
- Inputs and outputs separates a phase interface from the sockets used inside its graph.
- Calling child phases documents
questPhaseNodeDefinitionand the parent/child handoff. - Prefab dependencies explains root-owned prefab
scope,
phasePrefabs, andphaseInstancePrefabswithout inventing a universal inheritance rule. - Completion and interruption reviews terminating outputs, parent continuation, re-entry, and cut obligations.
- Complex cleanup, interruption, and cancellation expands those obligations into an owner ledger for scenes, communities, AI, devices, markers, vehicles, monitors, and durable state.
- Lab 4: Handoff Point moves Lab 3’s world activity into an external child and makes the boundary inspectable.
The practical pages target the exact
pinned version set: Cyberpunk 2077
2.31a for Windows (GOG; public patch 2.31), WolvenKit 8.19.0, ArchiveXL
1.27.0, RED4ext 1.30.0, and redscript 0.5.31.
What belongs in a questphase?
A questphase owns execution order and orchestration. Its nodes can change a fact, update a journal entry, wait for a condition, invoke a child phase, or coordinate another native resource.
The referenced resource remains authoritative:
| Concern | Authoritative owner | Questphase role |
|---|---|---|
| Quest and objective hierarchy | Journal resource | Change entry state through a typed journal path |
| Player-facing text | Localization resource | Trigger the state that presents it |
| Dialogue and cinematic timing | Scene resource | Launch the scene and receive an outcome |
| Community, trigger, or marker | World resource | Refer to it through a NodeRef and manage its lifecycle |
| Durable progression | Save-backed fact or journal state | Read or write it at deliberate boundaries |
That distinction is visible in Lab 1: the phase contains no title text and no location. Lab 4 adds an external child, but that child still does not contain trigger geometry or journal strings. It refers to the world and journal resources that own them.
The composition boundary
Lab 4 uses one registered root and one externally resolved child:
ArchiveXL quest.phases
-> cqa004.questphase registered root
phasePrefabs: [#cqa004_pr_handoff]
questPhaseNodeDefinition
phaseResource (Soft)
-> cqa004_boundary.questphase archived child, not registered
phasePrefabs: []
In1 ... Out1
Out1 from child -> parent continuation
Registration answers how the root becomes reachable. phaseResource answers
which child the parent loads. The archive must contain both files at their
exact depot paths, but only the root appears under ArchiveXL quest.phases.
Questphase resource anatomy
A .questphase file is a CR2W resource whose root chunk is
questQuestPhaseResource. Its executable graph is only one part of that root.
The outer resource
These focused excerpts show the two completed Lab 4 phase resources. Serialization metadata and handle details are omitted:
cqa004.questphase
questQuestPhaseResource
├── cookingPlatform: PLATFORM_PC
├── graph: questGraphDefinition
│ └── nodes: [12 nodes]
├── inplacePhases: []
└── phasePrefabs:
└── questQuestPrefabEntry
└── prefabNodeRef: #cqa004_pr_handoff
cqa004_boundary.questphase
questQuestPhaseResource
├── cookingPlatform: PLATFORM_PC
├── graph: questGraphDefinition
│ └── nodes: [10 nodes]
├── inplacePhases: []
└── phasePrefabs: []
| Property | Supported interpretation |
|---|---|
cookingPlatform | Target cooking platform recorded by the resource |
graph | Handle to the executable questGraphDefinition |
inplacePhases | Storage associated with inline/in-place phases; empty in Labs 1–5 |
phasePrefabs | Quest-prefab roots declared by this resource; the exact active composition determines which descendants can resolve against that scope |
Structurally validated: Lab 4’s root contains one prefab entry and its external child contains none. Empty arrays are meaningful serialized values, not omitted work and not a universal rule.
The graph
questGraphDefinition.nodes contains handles to node definitions. Each node
definition has:
- a graph-local integer
id; - one or more
questSocketDefinitionobjects; - properties specific to its node type.
Connections are stored from a source socket to a destination socket. The corresponding socket objects retain the connection handle, so a valid serialized graph must be internally consistent in both directions.
source node
output questSocketDefinition
graphGraphConnectionDefinition
source -> that output socket
destination -> input socket on destination node
The graph-local id is the useful identity when discussing behavior.
HandleId and HandleRefId are CR2W serialization references and can change
during a WolvenKit round trip. See
Identifier domains before comparing raw
exports.
Nodes are typed records
Common fields do not make all nodes interchangeable. A condition node has a condition payload and branch sockets; a journal node has a typed journal operation and path; a phase node has either an external resource reference or an inline graph arrangement.
Lab 4 uses these types across its two graphs:
| Node type | Decisive payload |
|---|---|
questInputNodeDefinition | Exposed phase input name |
questOutputNodeDefinition | Exposed phase output name and output type |
questConditionNodeDefinition | Immediate completion-fact comparison |
questJournalNodeDefinition | Journal entry type, path, and incoming state socket |
questPauseConditionNodeDefinition | Realtime delay or state-shaped trigger condition |
questMappinManagerNodeDefinition | Journal map-pin path and active/inactive operation |
questFactsDBManagerNodeDefinition | Exact fact write operation |
questPhaseNodeDefinition | External child path, interface sockets, and instance fields |
The Lab 4 overview explains every supplied node. The raw WolvenKit JSON beside each completed project is a review artifact, not a second reader authoring format.
Anatomy of an external phase node
Lab 4’s parent node uses this focused shape:
questPhaseNodeDefinition
├── id: 13
├── phaseGraph: null
├── phaseInstancePrefabs: []
├── phaseResource:
│ ├── DepotPath: mod\cqa\cqa004\phases\cqa004_boundary.questphase
│ └── Flags: Soft
├── saveLock: 0
├── sockets: [CutDestination, In1, Out1]
└── unfreezingTriggerNodeRef: 0
phaseGraph: null distinguishes this focused external-child shape from an
inline graph. The soft depot path is not ArchiveXL registration. It is the
parent resource’s reference to another archived CR2W resource.
saveLock: 0 and a zero unfreezing ref are exact values in the inspected
shape. Their broader behavior is not inferred here. Lab 4 also leaves
CutDestination unwired because the runtime semantics of a complete cut and
recovery route remain Experimental.
Sockets carry semantics
A socket is not merely a visual port. Its name and type tell the runtime how execution enters, branches, or leaves a node.
name: "Active" type: Input
name: "True" type: Output
name: "Out1" type: Output
name: "CutDestination" type: CutDestination
The layer matters. The child input node exposes interface name In1, then
emits internal graph flow through Out. The child’s terminating output node
accepts internal flow through In, then exposes interface name Out1 to its
parent. The parent phase node has its own In1 and Out1 sockets to match.
CutDestination belongs to interruption routing and is not an ordinary
success edge. Structural presence alone does not prove runtime cut behavior.
Inspect without over-reading
When reviewing a .questphase:
- Confirm
RootChunk.$typeisquestQuestPhaseResource. - Confirm
graph.Data.$typeisquestGraphDefinition. - Inventory every node by graph-local
idand RED type. - Record each connected source socket and destination socket.
- Inspect only the properties decisive for that node’s behavior.
- Review
inplacePhases, rootphasePrefabs, and every phase node’sphaseInstancePrefabsseparately. - For an external child, confirm
phaseGraphis null and the softphaseResourcepath resolves to an archived CR2W file. - Match the parent phase-node interface to child input/output node names.
- Round-trip each binary and compare semantics, not raw handle numbering.
A successful parse is Structurally validated evidence. It does not prove that the game resolves the path, preserves an active child through reload, or returns control to the parent.
Previous: Questphases. Next: Registering a root questphase.
Registering a root questphase
A mod-owned root questphase becomes reachable from the game’s root quest
structure through ArchiveXL registration. An external child becomes reachable
through the parent’s native phaseResource reference. Those are different
mechanisms.
Register exactly one composition root
Lab 4 registers only its parent:
quest:
phases:
- path: mod\cqa\cqa004\phases\cqa004.questphase
parent: base\quest\cyberpunk2077.quest
path is the depot path of the mod-owned root. parent is the vanilla root
quest resource under which ArchiveXL attaches it. The base-game resource is a
reference only; do not place an extracted copy in the project or archive.
The child does not receive a second registration entry:
mod\cqa\cqa004\phases\cqa004_boundary.questphase
It must still be packed at that exact depot path. Parent node 13 reaches it
through a soft phaseResource. Registering it independently would turn the
child into another root attachment instead of merely satisfying the parent
reference.
Registration and resolution are different edges
ArchiveXL quest.phases
-> cqa004.questphase
questPhaseNodeDefinition.phaseResource (Soft)
-> cqa004_boundary.questphase in archive
| Edge | Owner | What failure looks like |
|---|---|---|
| Root attachment | Loose .archive.xl | Root never becomes reachable or ArchiveXL logs registration error |
| Child resolution | Parent questPhaseNodeDefinition plus packed archive | Parent reaches the phase node but the depot reference cannot load |
The archive manifest should list both phase resources. The ArchiveXL
quest.phases list should contain only cqa004.questphase. Lab 4’s validator
rejects an independently registered child so the teaching boundary cannot
silently drift.
Registration is not content ownership
Lab 4 also registers its journal, onscreen localization, and streaming block:
journal:
- mod\cqa\cqa004\journal\cqa004.journal
localization:
onscreens:
en-us:
- mod\cqa\cqa004\localization\en-us\onscreens\cqa004.json
streaming:
blocks:
- mod\cqa\cqa004\world\cqa004_handoff.streamingblock
These operations answer separate questions:
| Registration | Makes available |
|---|---|
quest.phases | Executable root questphase |
journal | Journal tree and entries |
localization.onscreens | Resolved player-facing strings |
streaming.blocks | World block whose descriptors reach the two sectors |
ArchiveXL registers the streaming block, not its sector files separately. A root phase can register successfully and still fail because its child path, journal path, localization key, sector descriptor, or nested NodeRef is wrong.
Root does not mean location
Registration has no coordinate, radius, or NodeRef. Lab 4’s root gains world
scope through its phasePrefabs declaration, but the external child contains
the trigger conditions that use that scope. The world resources own the
actual trigger geometry and marker placement.
This separation is deliberate:
| Root owns | Child owns |
|---|---|
| Completion guard and first-run activation | Reach/leave activity |
| Root prefab declaration | Direct trigger and marker references |
| Starting the child | Returning Out1 |
| Post-handoff confirmation and final completion | Local objective and pin transitions |
A root needs a re-entry policy
Being attached to the root makes re-entry a design concern. Lab 4 keeps one
persistent fact, cqa004_completed:
root input
-> cqa004_completed == 0?
False -> terminating output
True -> activate quest -> run child -> confirm handoff
-> set cqa004_completed = 1 -> terminate
The write happens after the child has returned and after the parent’s 30-second confirmation step. A save made while the child is active and a save made in that parent confirmation window exercise different recovery boundaries.
Use an untouched save created before any Lab 4 candidate was installed. A console reset of the fact does not remove save-backed journal, phase, mappin, world, or active-node state.
Verification ladder
Check root composition in this order:
- Confirm the
.archive.xlregisters only the exact root depot path. - Confirm the packed archive contains both root and child at the manifest paths.
- Confirm the parent
phaseResource.DepotPathexactly matches the child. - Confirm the loose
.archive.xlis installed beside the archive as required by ArchiveXL. - Inspect ArchiveXL and RED4ext logs for registration and resource errors.
- Run from a save whose relevant facts and journal state are known.
- Observe child entry, child
Out1, and the first parent-only operation after the handoff. - Retain installed filenames and repeat the reload/streaming matrix.
Packing proves that both payloads exist. A clean ArchiveXL log proves the framework processed registration. Only the controlled game run proves native child resolution and the return route.
Previous: Questphase resource anatomy. Next: Inputs and outputs.
Inputs and outputs
Questphase inputs and outputs form an interface around a graph. They are
implemented by nodes inside the child resource and matched by sockets on the
parent’s questPhaseNodeDefinition.
Two socket layers
The names are easy to conflate:
parent phase node child graph boundary
----------------- --------------------
input socket In1 -> input socketName In1
ordinary socket Out -> child work
output socket Out1 <- output socketName Out1
child work -> ordinary socket In
questInputNodeDefinition.socketName names the entry exposed by the child.
Execution then leaves that node through its ordinary Out socket.
questOutputNodeDefinition.socketName names the result exposed by the child.
Execution reaches that node through its ordinary In socket.
Lab 4’s focused terminating-child contract is:
| Resource/node | Graph ID | Interface value | Internal or parent socket |
|---|---|---|---|
Child questInputNodeDefinition | 0 | socketName: In1 | emits internal Out |
Parent questPhaseNodeDefinition | 13 | input In1 | receives parent flow |
Child questOutputNodeDefinition | 1 | socketName: Out1, type: Terminating | receives internal In |
Parent questPhaseNodeDefinition | 13 | output Out1 | emits returned parent flow |
Those names are CName values. They are not graph-local IDs and not CR2W
handle IDs.
Entry is a contract, not an activation rule
The input node says where execution enters after a phase has been invoked. It does not decide why or when that invocation happens.
Activation policy belongs outside that boundary:
- ArchiveXL root attachment makes a root reachable;
- parent graph flow reaches the phase node’s
In1; - the parent node resolves its external child;
- the first child action begins only after the child enters through its
matching
socketName.
Lab 4’s child 0.Out activates the reach objective. The location behavior
comes from later IsInside and IsOutside conditions, not from In1.
Terminating output returns one outcome
With type: Terminating, reaching child output node 1.In ends that child
route. Its socketName: Out1 maps the result to parent node 13.Out1.
The parent resumes only through that matching phase-node output:
child 17.Out -> child 1.In
child exposes Out1
-> parent 13.Out1 -> parent 14.Active
The connection from 13.Out1 to the parent’s confirmation objective is
essential. A child output does not automatically select the next parent node.
Termination does not:
- reset facts;
- remove journal history;
- deactivate world dependencies automatically;
- invent an outgoing parent connection;
- prove the root will never be evaluated again.
Those are separate lifecycle decisions.
In1 and Out1 are not universal
Lab 4 deliberately uses the smallest focused interface: one input and one terminating output. Do not infer that every native phase node has both.
Observed in vanilla: extract these files from your own game:
base\open_world\street_stories\watson\northside_industrial_district\
sts_wat_nid_03\phases\sts_wat_nid_03.questphase
base\open_world\street_stories\watson\northside_industrial_district\
sts_wat_nid_03\phases\sts_wat_nid_03_openworld.questphase
The inspected root contains an external phase node with In1, Out1, and
CutDestination, and another external phase node with In1 and
CutDestination but no Out1. That second shape is enough to reject
“all phase nodes have In1/Out1” as an engine invariant.
Use In1/Out1 here because the parent must wait for one child activity and
then continue. Copy the interface contract appropriate to the lifecycle you
are implementing, not the nearest-looking graph.
Connect every named outcome
A child can expose several meaningful results only when the complete parent and child arrangement supports them:
child output "accepted" -> parent phase-node output "accepted" -> continuation
child output "declined" -> parent phase-node output "declined" -> cleanup
Every outcome requires:
- a reachable child output node with the intended
socketName; - a same-named output socket on the invoking phase node;
- an intentional parent connection or documented terminal policy;
- runtime evidence for the exact arrangement.
Lab 4 makes no multi-outcome claim. It returns only Out1.
Cut is a separate socket class
Lab 4’s input, output, and phase nodes retain structurally valid
CutDestination sockets. None is connected. CutDestination is not an alias
for Out1, and an unwired socket is not a tested interruption policy.
The presence and serialized type are Structurally validated. The runtime behavior of a wired cut, its propagation across the child boundary, and the required recovery state remain Experimental.
Review checklist
For each interface node, record:
| Question | Why it matters |
|---|---|
What is the socketName? | It is the cross-phase interface name |
| Which internal socket is connected? | It proves the internal route reaches the boundary |
Is the output Terminating? | It defines what reaching the boundary does to the child |
| Does the parent expose the same name? | A mismatch breaks the intended handoff |
| Where does the parent output lead? | A returned outcome still needs a continuation |
| Is a cut route wired? | Normal return and interruption are separate routes |
A phase can serialize while an intended outcome remains unreachable. Review reachability, exact names, and both resources together.
Previous: Registering a root questphase. Next: Calling child phases.
Calling child phases
A parent invokes an external child questphase with
questPhaseNodeDefinition. The node is both a depot-resource reference and
the parent side of the child’s interface.
External child shape
Lab 4 uses this exact focused shape:
questPhaseNodeDefinition
├── id: 13
├── sockets
│ ├── CutDestination unwired
│ ├── In1 parent enters here
│ └── Out1 child returns here
├── phaseGraph: null
├── phaseInstancePrefabs: []
├── phaseResource:
│ ├── DepotPath: mod\cqa\cqa004\phases\cqa004_boundary.questphase
│ └── Flags: Soft
├── saveLock: 0
└── unfreezingTriggerNodeRef: 0
| Property | Supported interpretation |
|---|---|
phaseResource | Soft depot reference to the external child resource |
phaseGraph | Inline graph slot; null in this external-child shape |
phaseInstancePrefabs | Prefab list on this phase-node instance; empty here |
saveLock | Exact serialized value 0; broader behavior is not generalized |
unfreezingTriggerNodeRef | Exact zero NodeRef; advanced behavior is not isolated |
Do not copy non-zero values for the final two fields from a large vanilla quest without isolating their activation and save semantics.
The child is archived, not independently registered
The root and child are separate CR2W files and both must appear in the packed archive:
mod\cqa\cqa004\phases\cqa004.questphase
mod\cqa\cqa004\phases\cqa004_boundary.questphase
Only the first appears under ArchiveXL quest.phases. The second is resolved
when the parent phase node follows phaseResource.
This distinction prevents two different ownership models from being confused:
- root registration attaches a new composition root under
base\quest\cyberpunk2077.quest; - external resolution loads a child because an executing parent names its depot path.
If the child is missing from the archive, registration of the parent can still look correct. If the child is separately registered, the files may load while the intended one-root ownership model has already been changed.
The interface must match
The parent and child meet at names, not at graph IDs:
parent cqa004.questphase
[13] questPhaseNodeDefinition
In1 -> child questInputNodeDefinition.socketName "In1"
Out1 <- child questOutputNodeDefinition.socketName "Out1"
child cqa004_boundary.questphase
[0] Input socketName In1
Out -> reach/leave activity -> In
[1] Output socketName Out1, type Terminating
Matching only the file path is insufficient. The parent node, child input, and child output must agree on the interface names, and the child route must actually reach its output.
Parent and child ownership
Split at a lifecycle boundary, not an arbitrary node count. Lab 4 makes the division concrete:
| Root parent owns | Boundary child owns |
|---|---|
| Completion guard | Reach and leave objective states |
| Quest and phase activation | Checkpoint pin activation/inactivation |
Root phasePrefabs declaration | Direct #cqa004_tr_reach and #cqa004_tr_leave use |
| Starting the child | Waiting for current inside/outside state |
Interpreting returned Out1 | Returning Out1 after leave succeeds |
| 30-second handoff confirmation | No post-return work |
| Completion fact and final quest state | No final quest completion |
The ownership table is a deliberate design, not a file-format restriction. It makes the active child interval and the parent-only continuation visible for save/reload testing.
Exact handoff sequence
parent [12] phase Active
-> parent [13].In1
-> child [0] socketName In1
-> reach objective and pin
-> IsInside reach
-> retire pin, succeed reach, activate leave
-> IsOutside leave
-> succeed leave
-> child [1] terminating socketName Out1
-> parent [13].Out1
-> parent [14] confirmation objective Active
The active confirmation objective is the visible proof that normal child return reached parent-only work. Thirty realtime seconds later, the parent succeeds that objective, phase, completion fact, and quest.
If the child never reaches output 1, parent node 13 never emits its normal
Out1. If 13.Out1 is orphaned, the child can finish without advancing the
larger quest. The diagram also keeps CutDestination visible but unwired; it
is not a hidden normal-return edge.
Root-owned prefab scope can cross this boundary
Lab 4’s parent root declares #cqa004_pr_handoff. The child directly uses
nested trigger NodeRefs while its own phasePrefabs and the parent node’s
phaseInstancePrefabs are empty.
See Prefab dependencies for the root, child, and phase-instance declaration locations.
External before inline
CR2W supports phaseGraph and inplacePhases, but this book begins with
external mod-owned child resources because they provide:
- an explicit depot path;
- an independently inspectable graph;
- a clear archive manifest entry;
- a focused parent/child diff;
- separate exact SVGs and structural fingerprints.
phaseGraph: null is therefore the documented external-child shape, not a
claim that the field must always be null. Inline phase activation, dependency
scope, save behavior, and interruption need their own isolated evidence.
Validation
Before runtime testing:
- Confirm the parent
phaseResource.DepotPathmatches the archived child. - Confirm the child is absent from ArchiveXL
quest.phases. - Confirm both roots are
questQuestPhaseResource. - Confirm external parent node
phaseGraphis null andphaseInstancePrefabsis empty. - Match every parent input and output name to a child interface node.
- Confirm each intended child route reaches an output.
- Confirm each returned parent output has a continuation.
- Inventory root and child prefab declarations separately.
- Confirm no ordinary edge uses
CutDestinationin the unwired lab. - Round-trip parent and child independently.
Then test entry, reload before and during the child, streaming away and back while it is active, reload after return, and completed re-entry. One normal handoff does not establish save or cut safety.
Previous: Inputs and outputs. Next: Prefab dependencies.
Prefab dependencies
Quest-prefab dependencies give quest graph NodeRefs a world-side scope. They are not required for graphs that never reference the world, and their native lifecycle cannot be reduced to “duplicate every direct use.”
Root-owned scope
questQuestPhaseResource.phasePrefabs contains
questQuestPrefabEntry records:
phasePrefabs:
- questQuestPrefabEntry
prefabNodeRef: #cqa004_pr_handoff
Lab 4 declares that entry on the registered root only:
cqa004.questphase
phasePrefabs: [#cqa004_pr_handoff]
invokes cqa004_boundary.questphase
cqa004_boundary.questphase
phasePrefabs: []
uses #cqa004_tr_reach and #cqa004_tr_leave
The root declaration can own the world scope used by an active external child.
It is therefore too narrow to define phasePrefabs as “only roots directly
used by nodes in this file.”
The supported statement is more precise:
A prefab declared by a composition root can remain available to external children in tested native arrangements. The exact root, child, and activation lifetime are part of the evidence.
That supports Lab 4’s design without claiming universal transitive inheritance.
Compare root and child declarations
Vanilla comparison
Observed in vanilla: extract these resources from your own game:
base\open_world\street_stories\watson\northside_industrial_district\
sts_wat_nid_03\phases\sts_wat_nid_03.questphase
base\open_world\street_stories\watson\northside_industrial_district\
sts_wat_nid_03\phases\sts_wat_nid_03_openworld.questphase
The inspected parent declares #sts_wat_nid_03_streetstory. Its external
open-world child has phasePrefabs: [], while the child graph uses nested refs
under that prefab. The invoking phase node also has
phaseInstancePrefabs: [].
This is comparative evidence for the serialized shape. Do not redistribute the extracted CR2W resources, and do not treat observation alone as a runtime test of Lab 4.
Lab 4 candidate
Structurally validated: the completed Lab 4 parent contains exactly one
root entry, the child contains zero, and phase node 13 contains zero
instance-prefab entries. Both phase resources cook and round-trip with
WolvenKit 8.19.0.
Experimental: mounting, child-time NodeRef resolution, streaming away and back, save/reload, and the eventual parent handoff remain pending until every Lab 4 runtime case passes.
Inspect all three declaration locations
| Native location | Lab 4 value |
|---|---|
Parent root phasePrefabs | [#cqa004_pr_handoff] |
Child root phasePrefabs | [] |
Parent phase node phaseInstancePrefabs | [] |
Duplication changes the candidate
An external child is a separate questQuestPhaseResource, so it can serialize
its own phasePrefabs list. Adding the same root there is not a harmless
annotation: it changes the activation arrangement that must be tested.
Use one deliberate shape:
- root-only declaration when evidence and desired lifetime match Lab 4;
- duplicated declaration only when the exact duplicate arrangement has an identified lifecycle reason and its own runtime evidence.
Do not copy every child dependency into every ancestor, and do not duplicate a
root merely because the child graph contains a local #... NodeRef.
Phase-instance dependencies are separate
questPhaseNodeDefinition.phaseInstancePrefabs is a second native dependency
location associated with the phase-node instance. It is not interchangeable
with either resource root’s phasePrefabs array.
The focused external-child examples used by this section keep
phaseInstancePrefabs: []. Other arrangements, including inline phases, need
separate evidence. Do not infer a universal precedence or fallback order among
the three lists.
A prefab entry does not create the world
Declaring a prefab root is one link in a longer reference chain. Lab 4 uses:
root phasePrefabs: #cqa004_pr_handoff
-> world-side full root: $/mod/cqa/cqa004/#cqa004_pr_handoff
-> Quest sector child refs:
#cqa004_tr_reach
#cqa004_tr_leave
-> AlwaysLoaded sector child ref:
#cqa004_mp_handoff
The streaming block and sectors still have to provide the binding, nodeRefs, placements, and descriptors. A valid root entry cannot repair a misspelled child ref, missing sector registration, or absent world resource. Conversely, a world object can exist while remaining unreachable from the phase.
Review procedure
For a composed phase tree:
- Inventory every non-zero NodeRef used by every phase resource.
- Group local
#...refs by the full root that gives them context. - Inventory each resource root’s
phasePrefabsand each invoking node’sphaseInstancePrefabsseparately. - Identify the composition root and the intended prefab activation lifetime.
- Confirm the world-side resources bind the same full root and child refs.
- Record whether each external child declares, duplicates, or omits the root.
- Cite runtime evidence for the exact arrangement; do not infer a general inheritance rule from one success.
- Test active-child reload, stream-away/return, normal completion, and cleanup from a controlled save.
World-aware examples retain depot paths and hashes for mod-owned resources. Vanilla comparisons cite paths and teach extraction; they never redistribute extracted CR2W files.
Previous: Calling child phases. Next: Completion and interruption.
Completion and interruption
A phase is complete only when execution reaches the intended output and its owner has performed the required state transitions. The last visible objective update is not an implicit return.
Normal child completion
Lab 4’s child ends with:
[16] wait until player IsOutside leave volume
-> [17] leave objective Succeeded
-> [1] terminating output socketName Out1
Reaching output node 1 terminates that child route and exposes outcome
Out1. It does not itself finish the root quest.
Parent continuation is the second half
The parent phase node must expose the same name and connect it onward:
child [1] socketName Out1
-> parent [13].Out1
-> parent [14] confirmation objective Active
-> [15] wait 30 realtime seconds
-> [16] confirmation objective Succeeded
-> [17] phase Succeeded
-> [18] set cqa004_completed = 1
-> [19] quest Succeeded
-> [1] terminating output
The confirmation window exists to make the ownership transition observable
and saveable. If objective cqa004_01_obj_confirm becomes active, normal
execution has returned to parent-only work. A reload during that 30-second
window tests a different boundary from a reload while the child is waiting on
a trigger.
Review both resources. A terminating child output with no matching parent socket cannot convey the intended result. A matching parent socket with no outgoing connection receives the result and then advances nowhere.
Termination is not cleanup
Reaching a questOutputNodeDefinition does not, by itself:
- inactivate objectives or mappins;
- deactivate a community;
- stop a scene or parallel monitor;
- reset a device’s persistent state;
- clear or set a completion fact;
- choose what a parent does next;
- unload every prefab declared by an ancestor.
Lab 4’s child performs its local presentation transitions before return: the checkpoint pin is retired, the reach objective is succeeded, and the leave objective is succeeded. The parent owns the remaining confirmation and final quest completion.
Every transient dependency needs an owner and release boundary. Persistent progress remains only because the re-entry design calls for it.
Write durable state before terminating
Lab 4 writes cqa004_completed = 1 after the child has returned and the
confirmation objective and phase have succeeded, but before the quest succeeds
and the root terminates:
phase Succeeded
-> set completion fact exactly to 1
-> quest Succeeded
-> terminate root route
Moving the terminating output earlier would make later writes unreachable. Moving the fact earlier would change which incomplete save boundaries take the already-completed bypass. The exact order is part of the recovery policy and must be tested, not inferred from one uninterrupted run.
Cut sockets are structural interruption surfaces
Many quest nodes expose a CutDestination socket. Its presence identifies a
distinct socket class; it does not supply a correct interruption policy.
Lab 4 retains the serialized cut sockets on its relevant nodes and connects
none of them. Its validator rejects any ordinary or cut edge involving
CutDestination.
| Claim | Evidence class |
|---|---|
The sockets exist with type CutDestination | Structurally validated |
| Similar sockets exist in extracted vanilla phases | Observed in vanilla |
| An unwired Lab 4 child cleans up correctly when externally interrupted | Experimental |
| A wired cut propagates across this parent/child boundary and reaches a particular recovery state | Experimental |
Do not draw a cut edge merely to make the graph look complete. Before wiring one, define:
- what work may be active when the cut arrives;
- which child, scene, or monitor receives interruption;
- which journal entries become inactive, failed, or resumable;
- which communities, markers, devices, and monitors are released;
- which durable fact distinguishes cut from normal completion;
- which child output tells the parent cleanup has finished;
- which save/reload cases prove the recovery policy.
That requires a separate fixture. Lab 4 teaches normal Out1 handoff only.
Re-entry across two active owners
Termination ends an execution route, not the lifetime of all save-backed state. A composed quest has more than one meaningful reload boundary:
| Save boundary | Expected active owner in Lab 4 |
|---|---|
| Before reaching checkpoint | Child, before IsInside resolves |
| Between reach and leave volumes | Child, before IsOutside resolves |
| After child return, during 30-second confirmation | Parent |
| After final completion | No first-run route; root guard bypasses |
The runtime protocol also streams away and returns while the child is active, then tests completed reload, identical reinstall, and a second untouched clean replay.
Use a save created before any Lab 4 candidate was installed. Changing files,
uninstalling, or writing cqa004_completed = 0 in a console does not erase
save-backed journal, graph, mappin, world, or persistent-state history.
Review before composition grows
Before adding another child or parallel branch, verify:
- every normal child route reaches an intentional output;
- every parent output has a continuation or deliberate terminal policy;
- all transient dependencies have cleanup owners;
- persistent writes occur before their route becomes unreachable;
- post-return parent work is distinguishable from active-child work;
- cut remains explicitly unsupported or has a tested policy;
- the exact version set, installed filenames, and clean starting save are recorded.
Previous: Prefab dependencies. Next: Complex cleanup, interruption, and cancellation.
Complex cleanup, interruption, and cancellation
Complex cleanup is not one final node. It is a protocol among graph routes and the resources they activated: scenes, communities, AI, workspots, devices, markers, vehicles, inventory, journal state, and save-backed progress.
Use this chapter when an activity has more than one live owner or more than one way to end. Its goal is a teardown design in which every outcome records its meaning, stops or neutralizes outstanding work, releases each resource through its actual owner, and returns through an explicit phase interface.
success / failure / player cancel / interruption
-> choose and persist one outcome
-> stop new state-changing work
-> let active consumers reach a safe release boundary
-> retire presentation
-> run owner-specific teardown
-> confirm cleanup handoff
-> write terminal state and terminate the owning route
There is no established native “clean up everything” operation. A cut edge, phase output, scene end, community deactivation, or vehicle command handles only its own bounded contract.
Vanilla references
These focused base-game resources are useful comparisons. Extract them from your own installation; they are references, not files to copy into the project.
base\open_world\minor_activities\watson\northside\ma_wat_nid_15\ma_wat_nid_15_phase.questphase
base\quest\minor_quests\mq003\phases\mq003_homeless.questphase
base\quest\minor_quests\mq003\scenes\mq003_01_homeless.scene
base\quest\side_quests\sq031\phases\sq031_porsche.questphase
base\open_world\street_stories\watson\northside_industrial_district\sts_wat_nid_03\phases\sts_wat_nid_03_gameplay.questphase
Name the exit before choosing the cleanup
Different endings require different state and sometimes different release orders:
| Exit class | What produces it | Required durable distinction |
|---|---|---|
| Normal success | Authoritative activity result | Success or branch outcome, written before destructive teardown |
| Authored failure | Death, target loss, timeout, combat state, or explicit failure fact | Failure reason and retry/terminal policy |
| Player cancellation | A supported prompt/UI/workspot/scene exit | Whether the activity remains active, resets, or terminates |
| Scene interruption and return | Scene interruption scenario plus quest wrapper outputs | Interrupted is not completed; return and terminal interruption need separate handling |
| Graph cut | questCutControlNodeDefinition.CutSource targeting named CutDestination sockets | Which routes are cut, whether the cut is permanent, and which cleanup route now owns them |
| Streaming transition | Player leaves a streamed owner while graph/save state remains active | Reacquire, remain inactive, or fail according to an explicit policy |
| Mod removal or upgrade | Archive changes while save-backed state remains | Migration/removal behavior, never inferred from a clean install |
Do not converge these exits before recording their meaning. If success and cancel both reach cleanup with no discriminator, a save taken during teardown cannot know which presentation, retry, or terminal state to restore.
Build an owner ledger
Before drawing edges, list every resource that may be active and the exact operation that acquired it:
| Active concern | Acquisition boundary | Release question |
|---|---|---|
| Parallel Pause Condition or delay | Split from a main route | Can a late signal remain harmless, or must a tested cut target it? |
| Journal objective/description/mappin | Journal or Mappin Manager state change | Which terminal state is correct for each outcome? |
| Scene | questSceneNodeDefinition input and scene entry | Which named normal, interruption, return, or failure output proves release? |
| Community actor or vehicle | Spawn Manager activation/phase action | Are scene, AI, workspot, combat, carry, or seat owners finished before deactivation? |
| Gameplay AI / follower role | AI manager command | Which later command or owner transfer ends that role safely? |
| Device interaction | Controller eligibility, player prompt, UI/workspot, or quest command | Preserve, restore, disconnect, or leave native state—which is evidenced? |
| World marker/trigger | Sector placement plus quest/journal activation | Retire the pin/presentation; do not mistake streaming for graph cancellation |
| Player-vehicle record | Player-vehicle system operation | Is the exact vehicle record and questEnablePlayerVehicle_NodeType context applicable? |
| Placed world vehicle | Sector, prefab, community, or scene | Which of those owners, not a guessed player-vehicle command, releases it? |
| Inventory/reward | Item or reward manager write | Is the write before or after commitment, and can any route repeat it? |
| Durable fact | Facts DB write and save | Does it mean outcome chosen, cleanup started, cleanup finished, or quest terminal? |
Give each row one cleanup owner. Shared responsibility is usually an unresolved handoff: the scene assumes the quest will retain an actor while the quest assumes scene End already released it.
Separate selection, quiescence, release, and finalization
A robust teardown has four boundaries.
1. Select one outcome
The winning branch records its outcome before it reaches shared teardown:
success signal -> set activity_outcome = success --+
failure signal -> set activity_outcome = failure --+-> cleanup entry
cancel signal -> set activity_outcome = cancel --+
Use facts, journal states, or explicit named outputs that the owning design can read after reload. Do not award, remove an item, or mark the whole quest complete merely to create a discriminator.
An XOR-shaped convergence is not proof that losing producers were cancelled. Every late route must either be harmless after the outcome write or be the target of a separately accepted cut topology.
2. Quiesce producers and consumers
Stop new state-changing work before removing the resources it addresses:
- make losing monitor outputs bypass journal, fact, reward, and world writes;
- wait for a scene’s intended named outcome rather than assuming section End released every performer;
- end or transfer follower, patrol, combat, workspot, carry, and seat roles;
- finish or explicitly interrupt device UI/personal-link/workspot ownership;
- stop spawning new waves before deactivating the encounter;
- decide how an active timer or delay behaves after the outcome is sealed.
Quiescence can be logical rather than destructive. A listener that remains active but cannot mutate state may be safer than an unproved cut operation.
3. Release owner-specific transients
Retire each resource through the system that owns it:
journal/mappin owner -> outcome-specific state + pin inactive
scene owner -> named exit / accepted interruption contract
AI/workspot owner -> explicit handoff or release command
community owner -> Spawn Manager Deactivate at safe boundary
device owner -> evidenced final controller/disconnect policy
vehicle owner -> source-specific deactivation or vehicle-system action
world owner -> leave placement to streaming unless a resource-specific action exists
The order may differ by branch. A failed combat encounter may need to stop waves before actor deactivation; a successful meeting may need the named scene exit, journal write, outer-area exit, and only then community deactivation.
4. Confirm handoff and finalize
Return from a cleanup child through a named output such as cleanup_done, then
let the parent write the terminal one-shot state and terminate. The output
proves only that the graph issued its cleanup sequence. Runtime observation
must still prove that actors, UI, vehicles, markers, and persistent state match
the intended result.
child cleanup_done
-> parent receives matching socket
-> terminal journal state
-> completion fact
-> root output
Write no required state after a terminating output; that route is unreachable. Also avoid writing the parent’s terminal fact before the child can finish its teardown, unless re-entry intentionally bypasses cleanup and that policy is tested.
Cut stops a graph target, not its world consequences
questCutControlNodeDefinition exposes ordinary flow plus CutSource.
Targets such as Pause Conditions expose CutDestination. The Cut Control also
serializes a permanent field.
Observed in vanilla: in the cited ma_wat_nid_15_phase.questphase, Cut
Control node 236 has a CutSource edge to character-killed listener node
225 at its CutDestination. Across the retained corpus, cut edges commonly
target Pause Conditions. This establishes explicit target wiring.
It does not establish that a cut:
- rolls back a fact or journal write already emitted;
- deactivates a community or removes a marker;
- stops a scene, AI command, vehicle, or device unless that exact target and consequence are separately evidenced;
- emits the target’s ordinary output;
- survives reload or permits re-entry in a particular way;
- is reversible when
permanentis false or irreversible when true.
Those semantics remain Experimental. If a late listener is cheap, make its post-outcome edge harmless. If it must be cut, isolate the listener and Cut Control in a mod-owned fixture and test both arrival orders, simultaneous signals, reload with both armed, and re-entry.
Resource-specific cleanup rules
Scenes
A scene section’s normal/cancel socket, an scnEndNode, a public named exit,
the quest wrapper’s Default INT/Default RET, and a quest
CutDestination are different interfaces. None implies community
deactivation or quest completion.
For a normal route, wait for the exact named scene output before advancing the outcome and releasing actors. For interruption, decide whether the scene may return, terminates into a named failure/cancel outcome, or remains unsupported. Active-line interruption and cut behavior in the current lab remain Experimental; do not silently route their unwired sockets into success.
See Cleanup and save state for the scene boundary matrix.
Actors, communities, AI, and workspots
questSpawnManagerNodeDefinition can carry an action whose
questCommunityTemplate_NodeType requests Deactivate. Its outgoing graph
edge proves command issuance, not immediate disappearance.
Observed in vanilla: mq003_homeless.questphase changes named community
phases and deactivates actors later in its lifecycle. Keep deactivation after
the scene or gameplay owner has released those actors.
Deactivate only after scenes, AI, workspots, combat, carry, and vehicle seats no longer need the actor. A timer alone is not a safe-release proof. Use a measured outer boundary or another authoritative state, and test dead, unconscious, missing, combat-active, streamed-out, and reloaded variants when the design supports them.
Keep a follower role active while the next activity still needs it. Clearing it early can return the actor to the community AI spot.
See Cleanup and character safety for actor-specific acceptance cases.
World markers, triggers, and streamed placements
Retire a quest mappin through its journal or Mappin Manager owner. A trigger is world geometry observed by a condition; completing the condition does not delete the trigger, and streaming it out does not cancel every graph listener that referenced it.
Placed world objects normally remain owned by their sector/prefab and stream according to that owner. Do not invent a generic despawn step for a trigger, marker, or prop. Instead, disable the player-facing state the quest activated, make remaining observers harmless, and test stream-away/return after every supported outcome.
Devices
Device cleanup is an authored final-state policy, not a universal reset. The device may need to preserve an open/unlocked/completed state, restore an evidence-matched state, disconnect a personal link, or remain under its native world owner.
Changing the archive does not clear device persistent state already retained by the save. Use a fresh identity or a save that never streamed it when testing package or controller changes. See Advanced devices, interactions, and persistent state.
Vehicles
Choose cleanup from the vehicle’s acquisition owner:
| Vehicle origin | Cleanup owner to investigate |
|---|---|
| Community/spawn-set vehicle | Community entry/phase and the actors/seats still using it |
| Placed world vehicle | Sector/prefab and any resource-specific quest operation |
| Player-vehicle record | Player-vehicle system and exact record state |
| Scene-staged vehicle | Scene exit plus quest handoff, occupants, and post-scene visibility |
The cited sq031_porsche.questphase is an Observed in vanilla context for
questEnablePlayerVehicle_NodeType, whose serialized payload includes a
vehicle record string and enable, despawn, and
makePlayerActiveVehicle. It is not a generic world-entity despawner.
Avoid intermediate vehicle cleanup while later stages still need a live vehicle. Give the final owner an explicit cleanup route targeting that vehicle.
Both observations are Runtime-proven only for their own archive and route. They do not prove the cause of the failure or a generic vehicle-cleanup recipe.
Journal, inventory, facts, and rewards
These writes are not visual cleanup; they are the durable meaning of the outcome. Order them deliberately:
- record the branch outcome;
- update the objective, description, or pin whose promise is now resolved;
- run safe resource teardown;
- return through the cleanup owner’s output;
- write the terminal one-shot fact and quest state at the owning parent;
- grant or remove inventory exactly once at its commitment boundary.
Failure and cancellation branches should not inherit success rewards or item removal merely because they converge on common cleanup.
Design cleanup as an idempotent protocol
Reload can resume between any two reachable graph writes. A cleanup protocol should therefore distinguish at least:
outcome_chosen
cleanup_started
cleanup_finished
activity_terminal
These may be explicit facts, journal states, or graph checkpoints rather than four literal fact names. The distinction matters more than the storage form.
Do not assume a cleanup command is idempotent. If a save can occur after the
command was issued but before cleanup_finished, test whether reload repeats
it, skips it, or restores the graph after it. A guard can prevent a second
quest-side emission, but it cannot prove what the device, actor, scene, or
vehicle already persisted.
An upgrade/removal path is a separate policy. A completion fact can prevent new activation, but it cannot erase an active scene, old controller state, community phase, vehicle instance, or journal state retained in a save.
Author and inspect the teardown in WolvenKit
- Duplicate a mod-owned test project and assign a fresh candidate identity. Do not edit an installed lab or copy a complete vanilla phase.
- Draw the owner ledger before editing the graph. Include every parallel listener, delay, scene, actor, AI/workspot, marker, device, vehicle, inventory write, journal entry, and fact.
- Give success, failure, cancel, timeout, and interruption distinct incoming routes. Write their discriminator before shared cleanup.
- For every independent listener, either gate all late writes on the chosen outcome or add a Cut Control only after the exact target behavior has an acceptance plan.
- Wire scene normal and supported interruption outputs explicitly. Leave
unsupported
Default INT,Default RET, andCutDestinationroutes visibly unwired and label them Experimental. - Release AI, workspot, carry, and seat ownership before community deactivation. Use a measured safe boundary where visible disappearance matters.
- Retire objectives and mappins according to each outcome. Do not let common
cleanup overwrite a deliberate
Failed,Succeeded, or resumable state. - Apply device and vehicle operations only to the exact controller/record and owner class for which the experiment has evidence.
- End a cleanup child with a named output and connect the matching parent socket. Put the parent’s terminal write before its output and after the child handoff.
- Save, close, reopen, and serialize every affected CR2W. Review concrete node types, socket classes, edges, NodeRefs, controller/record values, action arrays, output names, and the order of all durable writes.
- Cook, pack and inspect the exact mod-owned payload. Keep the archive inventory with your test notes.
The existing labs demonstrate smaller ownership boundaries. Combining their shapes into a mixed scene/actor/device/vehicle teardown creates a new Experimental candidate; structural validity does not inherit runtime results from its parts.
Clean-save, interruption, and reload matrix
Begin positive identity/topology testing from an untouched save created before the candidate was ever installed. Clone complete save slots only while the game is closed. Bind every row to the same archive filename unless the row is an explicit upgrade/removal case.
| Case | Required observation |
|---|---|
| Normal success | One outcome is written; presentation retires; every owner releases in order; terminal state occurs once |
| Each authored failure | Failure reason survives cleanup and reload; no success reward, item removal, or objective state leaks in |
| Player cancellation | Retry or terminal-cancel policy is exact; UI/workspot/scene state releases; no irreversible success write occurs |
| Monitor wins | Losing main-route signal cannot mutate state later |
| Main route wins | Losing monitor remains harmless or the tested cut handles it |
| Near-simultaneous signals | The observed result matches the only ordering on which the design relies |
| Save with all listeners armed | Reload restores or neutralizes each route without duplicate outcome writes |
| Save after outcome, before teardown | Reload resumes the correct cleanup branch and does not re-run activity success |
| Save after each owner release | Remaining owners finish; already released resources do not duplicate, vanish incorrectly, or regain ownership |
| Active scene interruption/return | Supported path retains actor and progression; unsupported path remains recorded as Experimental |
| Combat/death during cleanup | Actor, AI, wave, and deactivation policy reaches an intentional result |
| Vehicle occupied during cleanup | Occupants, seats, player control, and vehicle lifetime remain coherent |
| Device operation active during cleanup | UI/personal link/workspot and controller reach the authored final state |
| Stream away and return | No stale pin, duplicate spawn, restarted scene, repeated reward, or rearmed terminal listener |
| Completed-save reload | Root guard bypasses activity; cleanup does not replay; world/save state matches the claim |
| Upgrade/removal copy | Lingering save-backed state is documented and not misreported as clean-install behavior |
| Second untouched save | The exact candidate reproduces without inherited facts, journal, graph, scene, community, device, or vehicle state |
If the game forbids saving at a boundary, record that limitation and test the nearest supported checkpoint. An unexercised row remains Experimental.
Failure routing
| Symptom | Inspect first |
|---|---|
| Success and failure both write | Outcome discriminator placement, late listener gates, and convergence before state writes |
| Quest ends but marker/actor/vehicle remains | Phase output occurred before owner-specific teardown or targeted the wrong owner class |
| Actor disappears during dialogue/combat | Community deactivation precedes scene/AI/workspot/combat release |
| Scene ends but quest does not continue | Named exit, quest-wrapper output, child socket, and parent continuation are separate boundaries |
| Reload repeats reward or item removal | Commitment write is reachable twice or terminal/re-entry guard is too late |
| Reload skips required cleanup | Terminal fact was written before cleanup handoff or active graph resumed on the bypass path |
| Cut route produces an unexpected ordinary output | The design assumed unproved cut semantics; isolate and retain the exact target behavior |
| Vehicle disappears at another vehicle’s cleanup | Wrong acquisition owner or record domain; inspect cross-vehicle state and intermediate child handoff |
| Device returns to an old state | Reused persistent identity or save-backed controller state, not merely the current archive payload |
| Stream return duplicates the encounter | Activation/re-entry guard, community/scene state, and late monitor remain independently live |
Previous: Completion and interruption. Next: Lab 4: Handoff Point.
Lab 4: Handoff Point
Lab 4 moves Boundary Check’s reach/leave activity into an external child
questphase. The registered root starts the child, waits for its Out1 return,
then performs a visible parent-only confirmation before completing the quest.
| Record | Value |
|---|---|
| Structural validation date | 2026-08-09 |
| Runtime test date | Not yet recorded |
Structurally validated: both seven-resource checkpoints round-trip with
WolvenKit 8.19.0. Follow the test guide for child entry, return, streaming,
and save/reload.
Follow Author Handoff Point in WolvenKit to expand the start checkpoint. Then use Test Handoff Point to bind the candidate, saves, observations, and logs.
What the lab demonstrates
registered root -> completed == 0?
False -> terminate
True -> activate quest and phase
-> enter external child through In1
activate reach objective and pin
wait IsInside reach volume
retire pin; succeed reach; activate leave
wait IsOutside leave volume
succeed leave; terminate child through Out1
<- parent phase node emits Out1
-> activate confirmation objective
-> wait 30 realtime seconds
-> succeed confirmation and phase
-> set completed = 1
-> succeed quest; terminate root
The CutDestination socket exists on the external phase node and other
relevant nodes, but every cut socket is deliberately unwired. Lab 4 teaches a
normal terminating Out1 handoff only; cut behavior remains Experimental.
Required environment
| Component | Exact version |
|---|---|
| Cyberpunk 2077 for Windows (GOG) | 2.31a (public patch 2.31) |
| WolvenKit | 8.19.0 |
| ArchiveXL | 1.27.0 |
| RED4ext | 1.30.0 |
| redscript | 0.5.31 |
Other versions may expose different editor wrappers or runtime behavior.
Prerequisites and downloads
Complete Lab 3, then read:
-
Download the start checkpoint. It has the complete seven-resource scaffold, a three-node root, and a two-node pass-through child.
-
Download the completed checkpoint. It has the same resources, the exact 12-node root, and the exact 10-node child.
Do not install the two checkpoints together. They register and pack the same
depot paths. Use an untouched save created before any Lab 4 candidate was
first installed. Uninstalling an earlier candidate or setting
cqa004_completed = 0 does not remove save-backed journal, phase, mappin,
world, or graph state.
Resource ownership
| Resource | Depot path | Owns |
|---|---|---|
| Root questphase | mod\cqa\cqa004\phases\cqa004.questphase | Root graph, completion policy, external child call, and root prefab declaration |
| Boundary child questphase | mod\cqa\cqa004\phases\cqa004_boundary.questphase | Reach/leave activity and terminating Out1 |
| Journal | mod\cqa\cqa004\journal\cqa004.journal | Quest, phase, three objectives, and handoff map pin |
| Onscreen localization | mod\cqa\cqa004\localization\en-us\onscreens\cqa004.json | Quest title, three objective strings, and pin caption |
| Streaming block | mod\cqa\cqa004\world\cqa004_handoff.streamingblock | Quest and AlwaysLoaded sector descriptors |
| Quest sector | mod\cqa\cqa004\world\cqa004_handoff.streamingsector | Reach and leave trigger definitions and placements |
| AlwaysLoaded sector | mod\cqa\cqa004\world\cqa004_always_loaded.streamingsector | Static handoff marker definition and placement |
| ArchiveXL registration | CQA_Lab04_HandoffPoint.archive.xl | Root attachment, journal/localization merge, and streaming-block registration |
The loose ArchiveXL file is packaging metadata, not an eighth CR2W resource. ArchiveXL registers the streaming block, not its sectors separately.
Registration and child resolution
The ArchiveXL file registers only the root phase:
quest:
phases:
- path: mod\cqa\cqa004\phases\cqa004.questphase
parent: base\quest\cyberpunk2077.quest
journal:
- mod\cqa\cqa004\journal\cqa004.journal
localization:
onscreens:
en-us:
- mod\cqa\cqa004\localization\en-us\onscreens\cqa004.json
streaming:
blocks:
- mod\cqa\cqa004\world\cqa004_handoff.streamingblock
Parent phase node 13 resolves the child through:
phaseGraph: null
phaseResource:
DepotPath: mod\cqa\cqa004\phases\cqa004_boundary.questphase
Flags: Soft
phaseInstancePrefabs: []
saveLock: 0
unfreezingTriggerNodeRef: 0
The child is packed but not independently ArchiveXL-registered. The supplied validator rejects a child registration entry.
Prefab and NodeRef contract
The registered root declares one local prefab root:
#cqa004_pr_handoff
The external child declares:
phasePrefabs: []
The world-side full root and nested references are:
$/mod/cqa/cqa004/#cqa004_pr_handoff
#cqa004_tr_reach
#cqa004_tr_leave
#cqa004_mp_handoff
The child directly uses the two local trigger refs. Its empty prefab list is intentional: Lab 4 tests root-owned scope across the child boundary. The arrangement is Structurally validated here, Observed in vanilla in the cited street-story pair, and Runtime-proven in the retained four-child GQT003 fixture. Lab 4’s own runtime result remains Experimental.
World and journal scaffold
The world resources reuse Lab 3’s checked geometry under the cqa004
namespace:
| Owner | Node | Placement | Geometry or orientation |
|---|---|---|---|
| Quest sector | worldTriggerAreaNode reach | (-1000.02, 1497.2208, 2.3) | 16-point regular outline, 25 m circumradius, 12 m high |
| Quest sector | worldTriggerAreaNode leave | (-1000.02, 1497.2208, 0.3) | 20-point regular outline, 110 m circumradius, 16 m high |
| AlwaysLoaded sector | worldStaticMarkerNode | (-1000.02, 1497.2208, 8.3) | yaw 88.6° |
The typed journal tree is:
quests/minor_quest/cqa004
└── cqa004_01
├── cqa004_01_obj_reach
│ └── cqa004_01_qmp_handoff -> #cqa004_mp_handoff
├── cqa004_01_obj_leave
└── cqa004_01_obj_confirm
The child owns the first two objectives and map pin. The parent owns the
confirmation objective. The only FactsDB name is cqa004_completed.
Start checkpoint contract
The start root already teaches external resolution without the completed behavior:
| Root ID | RED type | Purpose |
|---|---|---|
0 | questInputNodeDefinition | Expose root In1 |
13 | questPhaseNodeDefinition | Resolve child and match In1/Out1 |
1 | questOutputNodeDefinition | Terminate root through Out1 |
Its two edges are 0.Out -> 13.In1 and 13.Out1 -> 1.In.
The start child contains [0] Input and [1] Output, connected by
0.Out -> 1.In. Output 1 is Terminating. This pass-through is a checkpoint
for inspecting both interface layers; it is not the completed player activity.
Exact root phase
| ID | RED node type | Decisive payload | Purpose |
|---|---|---|---|
0 | questInputNodeDefinition | interface In1 | Enter registered root |
1 | questOutputNodeDefinition | terminating Out1 | End success and bypass routes |
10 | questConditionNodeDefinition | cqa004_completed Equal 0 | Bypass a completed save |
11 | questJournalNodeDefinition | quest path | Activate Handoff Point |
12 | questJournalNodeDefinition | phase path | Activate phase |
13 | questPhaseNodeDefinition | soft child path; null inline graph; empty instance prefabs | Invoke Boundary child through In1 and receive Out1 |
14 | questJournalNodeDefinition | confirmation objective | Prove parent-only continuation became active |
15 | questPauseConditionNodeDefinition | 30-second realtime delay | Keep the post-return parent window observable |
16 | questJournalNodeDefinition | confirmation objective | Succeed confirmation |
17 | questJournalNodeDefinition | phase path | Succeed phase |
18 | questFactsDBManagerNodeDefinition | set cqa004_completed = 1 exactly | Persist one-shot completion |
19 | questJournalNodeDefinition | quest path | Succeed Handoff Point |
Root edge contract
| Source | Destination | Meaning |
|---|---|---|
0.Out | 10.In | Enter completion guard |
10.False | 1.In | Completed-save bypass |
10.True | 11.Active | First-run activation |
11.Out | 12.Active | Activate phase |
12.Out | 13.In1 | Enter external child |
13.Out1 | 14.Active | Resume parent after normal child return |
14.Out | 15.In | Begin visible parent-only window |
15.Out | 16.Succeeded | End confirmation wait |
16.Out | 17.Succeeded | Succeed phase |
17.Out | 18.In | Write durable completion |
18.Out | 19.Succeeded | Succeed quest |
19.Out | 1.In | Terminate successful root route |
Exact boundary child phase
| ID | RED node type | Decisive payload | Purpose |
|---|---|---|---|
0 | questInputNodeDefinition | interface In1 | Receive parent invocation |
1 | questOutputNodeDefinition | terminating Out1 | Return normal completion |
10 | questJournalNodeDefinition | reach objective | Activate reach objective |
11 | questMappinManagerNodeDefinition | pin path; disable previous 0 | Activate and track handoff pin |
12 | questPauseConditionNodeDefinition | player IsInside #cqa004_tr_reach | Wait for current inside state |
13 | questMappinManagerNodeDefinition | same pin path; disable previous 0 | Inactivate handoff pin |
14 | questJournalNodeDefinition | reach objective | Succeed reach objective |
15 | questJournalNodeDefinition | leave objective | Activate leave objective |
16 | questPauseConditionNodeDefinition | player IsOutside #cqa004_tr_leave | Wait for current outside state |
17 | questJournalNodeDefinition | leave objective | Succeed leave objective before return |
Child edge contract
| Source | Destination | Meaning |
|---|---|---|
0.Out | 10.Active | Begin child-owned activity |
10.Out | 11.Active | Activate pin |
11.Out | 12.In | Arm reach condition |
12.Out | 13.Inactive | Retire pin after reach |
13.Out | 14.Succeeded | Succeed reach objective |
14.Out | 15.Active | Activate leave objective |
15.Out | 16.In | Arm leave condition |
16.Out | 17.Succeeded | Succeed leave objective |
17.Out | 1.In | Terminate child through Out1 |
No root edge reaches a child node directly. No child edge reaches a parent node
directly. Phase node 13 and the matching interface names are the handoff.
Common failure modes
| Symptom | Check first | Boundary |
|---|---|---|
| Quest never appears | Root path in .archive.xl, installed archive pair, ArchiveXL/RED4ext logs | The child is not an additional root registration |
| Quest activates but child work never starts | Node 13 soft path, archived child, parent In1, child socketName: In1 | A valid parent CR2W does not prove external resolution |
| Child finishes but confirmation never appears | Child output reachability, child socketName: Out1, parent 13.Out1 -> 14.Active | Journal success is not implicit parent continuation |
| Trigger refs fail only in child | Root prefab entry, empty child/instance lists, full world binding, exact installed build | Do not “fix” by duplicating until the tested arrangement is diagnosed |
| Reload restarts or skips work | Save history, active owner at save time, fact write order, journal state | A console fact reset is not a clean save |
| An interruption route behaves unpredictably | Confirm every CutDestination remains unwired | Lab 4 makes no cut-safety claim |
Lab sequence: All labs · Previous topic: Complex cleanup, interruption, and cancellation · Next: Author Handoff Point in WolvenKit.
Author Handoff Point in WolvenKit
This walkthrough expands the supplied Lab 4 start checkpoint into the exact 12-node parent and 10-node child. The start project already contains every world, journal, localization, registration, and phase resource so the work can focus on the native composition boundary.
| Record | Value |
|---|---|
| Structural validation date | 2026-08-09 |
| Runtime test date | Not yet recorded |
Required environment
| Component | Exact version |
|---|---|
| Cyberpunk 2077 for Windows (GOG) | 2.31a (public patch 2.31) |
| WolvenKit | 8.19.0 |
| ArchiveXL | 1.27.0 |
| RED4ext | 1.30.0 |
| redscript | 0.5.31 |
Use an untouched save created before any Lab 4 candidate was installed for
runtime testing. A manually edited _Start project is useful for learning and
comparison, but only the unmodified completed checkpoint is the canonical
acceptance candidate.
1. Open and isolate the checkpoint
- Download and extract the start ZIP.
- Open
CQA_Lab04_HandoffPoint_Start.cpmodprojwith File > Open Project. - Make an untouched backup of the extracted directory.
- Confirm
source\archiveandsource\raweach contain the seven depot resources listed in the overview. - Confirm
CQA_Lab04_HandoffPoint_Start.archive.xlis undersource\resources. - Do not install the start and completed checkpoints together.
The start parent is [0] Input -> [13] Phase -> [1] Output. The start child is
[0] Input -> [1] Output. Both output nodes use socketName: Out1 and
type: Terminating.
2. Audit registration before graph work
Open the ArchiveXL file and retain these four sections:
quest:
phases:
- path: mod\cqa\cqa004\phases\cqa004.questphase
parent: base\quest\cyberpunk2077.quest
journal:
- mod\cqa\cqa004\journal\cqa004.journal
localization:
onscreens:
en-us:
- mod\cqa\cqa004\localization\en-us\onscreens\cqa004.json
streaming:
blocks:
- mod\cqa\cqa004\world\cqa004_handoff.streamingblock
Do not add cqa004_boundary.questphase to quest.phases. It is an archived
child resolved by the parent, not a second root. Do not add the two sectors to
a guessed top-level registration list; the block descriptors reach them.
3. Audit all seven native resources
Before changing either graph, identify each supplied owner:
| Resource | Check |
|---|---|
cqa004.questphase | Registered root, one phasePrefabs entry, external phase node |
cqa004_boundary.questphase | Unregistered external child, empty phasePrefabs |
cqa004.journal | Quest, phase, reach, leave, confirmation, and map-pin entries |
cqa004.json | Five exact secondary keys and English strings |
cqa004_handoff.streamingblock | Quest-sector and AlwaysLoaded-sector descriptors |
cqa004_handoff.streamingsector | Two trigger nodes, placements, refs, and outlines |
cqa004_always_loaded.streamingsector | Static marker, placement, and marker ref |
The checkpoint includes cooked resources for direct WolvenKit editing and raw CR2W-JSON for focused review. Do not edit the JSON as the tutorial workflow.
4. Audit journal and localization ownership
Open cqa004.journal and confirm this typed tree:
gameJournalRootFolderEntry
└── quests gameJournalPrimaryFolderEntry
└── minor_quest gameJournalFolderEntry
└── cqa004 gameJournalQuest
└── cqa004_01 gameJournalQuestPhase
├── cqa004_01_obj_reach
│ └── cqa004_01_qmp_handoff
├── cqa004_01_obj_leave
└── cqa004_01_obj_confirm
The map pin’s reference is #cqa004_mp_handoff. Open the localization
resource and confirm:
| Secondary key | English value |
|---|---|
cqa_cqa004_title | Handoff Point |
cqa_cqa004_objective_reach | Reach the handoff point. |
cqa_cqa004_objective_leave | Clear the handoff area. |
cqa_cqa004_objective_confirm | Wait for handoff confirmation. |
cqa_cqa004_mappin_handoff | Handoff Point |
The child later changes reach, pin, and leave state. The parent later changes quest, phase, and confirmation state. Neither phase owns the strings.
5. Audit prefab scope and world resources
Open the root phase’s top-level phasePrefabs and retain exactly one
questQuestPrefabEntry:
#cqa004_pr_handoff
Open the child and confirm phasePrefabs: []. Open parent phase node 13 and
confirm phaseInstancePrefabs: []. Do not duplicate the root into either
empty list.
The streaming block’s Quest descriptor uses full root:
$/mod/cqa/cqa004/#cqa004_pr_handoff
The two sector resources supply these full child refs:
$/mod/cqa/cqa004/#cqa004_pr_handoff/#cqa004_tr_reach
$/mod/cqa/cqa004/#cqa004_pr_handoff/#cqa004_tr_leave
$/mod/cqa/cqa004/#cqa004_pr_handoff/#cqa004_mp_handoff
The trigger geometry and marker placement intentionally match Lab 3. Confirm their values against the Lab 4 overview instead of changing location and phase ownership in the same experiment.
6. Audit the existing external phase node
Open cqa004.questphase, select node 13, and inspect its serialized
properties:
| Property | Exact value |
|---|---|
| RED type | questPhaseNodeDefinition |
phaseGraph | null |
phaseInstancePrefabs | empty |
phaseResource.DepotPath | mod\cqa\cqa004\phases\cqa004_boundary.questphase |
phaseResource.Flags | Soft |
saveLock | 0 |
unfreezingTriggerNodeRef | zero NodeRef |
| interface sockets | input In1, output Out1 |
| interruption socket | CutDestination, unwired |
Open the child and confirm input node 0 exposes socketName: In1, output
node 1 exposes socketName: Out1, and output type is Terminating.
The figure shows both interface layers and the present-but-unwired
CutDestination. Do not connect cut to the terminating output as a shortcut.
7. Add and number parent nodes
Keep parent nodes 0, 1, and 13. Add these nine nodes:
| Target ID | Add Node label | RED type |
|---|---|---|
10 | Condition | questConditionNodeDefinition |
11 | Journal | questJournalNodeDefinition |
12 | Journal | questJournalNodeDefinition |
14 | Journal | questJournalNodeDefinition |
15 | PauseCondition | questPauseConditionNodeDefinition |
16 | Journal | questJournalNodeDefinition |
17 | Journal | questJournalNodeDefinition |
18 | FactsDBManager | questFactsDBManagerNodeDefinition |
19 | Journal | questJournalNodeDefinition |
WolvenKit may assign lower automatic IDs. Renumber safely in two passes: move
new nodes to temporary IDs 100–108, then move them to the targets. Never
overwrite an ID still owned by another node. Save, close, reopen, and confirm
the inventory before wiring.
8. Configure the parent guard and fact write
At parent ID 10, add questFactsDBCondition, then
questVarComparison_ConditionType:
| Property | Value |
|---|---|
factName | cqa004_completed |
comparisonType | Equal |
value | 0 |
At parent ID 18, choose questSetVar_NodeType:
| Property | Value |
|---|---|
factName | cqa004_completed |
setExactValue | 1 |
value | 1 |
The exact write occurs only after child return and confirmation. Do not add a second fact to the child.
9. Configure parent journal operations
For parent Journal nodes, add questJournalQuestEntry_NodeType and a typed
gameJournalPath. Keep these common values:
| Property | Value |
|---|---|
optional | 0 |
sendNotification | 1 |
trackQuest | 1 |
version | Initial |
path.editorPath | empty |
path.fileEntryIndex | 2 |
| IDs | className | realPath | Connected state |
|---|---|---|---|
11, 19 | gameJournalQuest | quests/minor_quest/cqa004 | 11.Active, 19.Succeeded |
12, 17 | gameJournalQuestPhase | quests/minor_quest/cqa004/cqa004_01 | 12.Active, 17.Succeeded |
14, 16 | gameJournalQuestObjective | quests/minor_quest/cqa004/cqa004_01/cqa004_01_obj_confirm | 14.Active, 16.Succeeded |
Incoming state sockets choose the requested journal state. The repeated path does not make the nodes redundant.
10. Configure the parent confirmation delay
At parent ID 15, add questTimeCondition, then
questRealtimeDelay_ConditionType:
| Property | Value |
|---|---|
hours | 0 |
minutes | 0 |
seconds | 30 |
miliseconds | 0 |
The 30-second realtime window makes the returned parent owner observable and provides a dedicated post-return reload case. It is not child cleanup.
11. Wire the exact parent graph
Right-click nodes and use Show Unused Sockets, then create these twelve ordinary edges:
| Source | Destination |
|---|---|
0.Out | 10.In |
10.False | 1.In |
10.True | 11.Active |
11.Out | 12.Active |
12.Out | 13.In1 |
13.Out1 | 14.Active |
14.Out | 15.In |
15.Out | 16.Succeeded |
16.Out | 17.Succeeded |
17.Out | 18.In |
18.Out | 19.Succeeded |
19.Out | 1.In |
The decisive handoff edge is 13.Out1 -> 14.Active. Leave every
CutDestination unwired.
Save, close, reopen, then compare against the exact parent graph. Canvas position and handle IDs are not behavior.
12. Add and number child nodes
Open cqa004_boundary.questphase. Keep nodes 0 and 1, then add:
| Target ID | Add Node label | RED type |
|---|---|---|
10 | Journal | questJournalNodeDefinition |
11 | MappinManager | questMappinManagerNodeDefinition |
12 | PauseCondition | questPauseConditionNodeDefinition |
13 | MappinManager | questMappinManagerNodeDefinition |
14 | Journal | questJournalNodeDefinition |
15 | Journal | questJournalNodeDefinition |
16 | PauseCondition | questPauseConditionNodeDefinition |
17 | Journal | questJournalNodeDefinition |
Use temporary IDs 100–107 before assigning final IDs. Save and reopen the
child before adding connections.
13. Configure child journal operations
Use the same common journal values as the parent. Configure:
| IDs | className | realPath | Connected state |
|---|---|---|---|
10, 14 | gameJournalQuestObjective | quests/minor_quest/cqa004/cqa004_01/cqa004_01_obj_reach | 10.Active, 14.Succeeded |
15, 17 | gameJournalQuestObjective | quests/minor_quest/cqa004/cqa004_01/cqa004_01_obj_leave | 15.Active, 17.Succeeded |
Child node 17 succeeds its local objective before the terminating output. It
does not succeed the parent phase or root quest.
14. Configure child map-pin operations
Both Mappin Manager nodes use:
| Property | Value |
|---|---|
path.className | gameJournalQuestMapPin |
path.realPath | quests/minor_quest/cqa004/cqa004_01/cqa004_01_obj_reach/cqa004_01_qmp_handoff |
path.fileEntryIndex | 2 |
path.editorPath | empty |
disablePreviousMappins | 0 |
Node 11 receives Active; node 13 receives Inactive. They address the
same journal map pin. This isolated lab has no deliberate prior route to
replace.
15. Configure child trigger waits
At each Pause Condition, add questTriggerCondition:
| ID | condition.type | triggerAreaRef |
|---|---|---|
12 | IsInside | #cqa004_tr_reach |
16 | IsOutside | #cqa004_tr_leave |
Set isPlayerActivator: 1. Leave activatorRef as the ordinary empty
gameEntityReference. These are state-shaped waits, not Entered/Exited
transition events.
The refs are local to #cqa004_pr_handoff, which is declared by the parent
root. Keep child phasePrefabs empty for this lab.
16. Wire the exact child graph
Use Show Unused Sockets, then create these nine ordinary edges:
| Source | Destination |
|---|---|
0.Out | 10.Active |
10.Out | 11.Active |
11.Out | 12.In |
12.Out | 13.Inactive |
13.Out | 14.Succeeded |
14.Out | 15.Active |
15.Out | 16.In |
16.Out | 17.Succeeded |
17.Out | 1.In |
Leave every CutDestination unwired. Save, close, reopen, and compare with the
exact child graph.
17. Review the two resources together
Confirm all of the following before building:
- parent node
13.In1matches child inputsocketName: In1; - child output
socketName: Out1is terminating; - parent node
13.Out1connects only to parent14.Active; phaseResourceis soft and matches the archived child path exactly;phaseGraphis null;- parent phase-node instance prefabs and child root prefabs are empty;
- only the root declares
#cqa004_pr_handoff; - no edge uses
CutDestination; - the child is absent from ArchiveXL
quest.phases.
The exact contract figure should now match the resources:
18. Validate, build, and install
With every tab saved:
- run Project > Scan for broken file references;
- run Project > Scan for broken files;
- run Project > Run File Validation on the entire project;
- resolve all project-owned errors;
- close Cyberpunk 2077;
- choose Build > Install and wait for packing and installation;
- inspect WolvenKit’s Log panel and both installed files.
Use the normal ArchiveXL project install, not Install as REDmod. A packed
archive without its loose .archive.xl cannot attach the root, merge the
journal/localization, or register the world block.
The authored start project installs _Start-named files. To test the supplied
completed project, open CQA_Lab04_HandoffPoint.cpmodproj, remove the start
project’s installed files, and install the following pair:
archive\pc\mod\CQA_Lab04_HandoffPoint.archive
archive\pc\mod\CQA_Lab04_HandoffPoint.archive.xl
Common authoring failures
| Symptom | Check |
|---|---|
| Parent activates but child does not | Exact soft depot path, packed child, null phaseGraph, matching In1, and logs |
| Child finishes but confirmation does not start | Child output reachability, terminating Out1, and 13.Out1 -> 14.Active |
| Trigger refs fail after the split | Parent root prefab entry, empty child/instance lists, world full root, and exact child refs |
| Both phases start independently | Remove the child from ArchiveXL quest.phases; only the root is registered |
| Completion bypass runs too early | Confirm guard is Equal 0 and fact write occurs at parent ID 18 |
| Cut seems to terminate normally | Remove the guessed cut edge; Lab 4 leaves every cut socket unwired |
| Graph changes after reopen | Renumber through temporary IDs, save/reopen before wiring, then compare semantics |
| Canonical hashes do not match | Use the unmodified completed checkpoint; a manually expanded _Start build is not byte-identical evidence |
Lab sequence: All labs · Previous: Lab 4: Handoff Point · Next: Test Handoff Point.
Test Handoff Point
Lab 4 crosses root registration, external child resolution, root-owned prefab scope, world streaming, journal/mappin state, and two saveable phase owners. A final quest screenshot cannot identify which boundary worked. This protocol binds the installed candidate, clean-save lineage, visible handoff, and fresh logs across eight executions.
| Record | Value |
|---|---|
| Structural validation date | 2026-08-09 |
| Runtime test date | Not yet recorded |
Use the unmodified completed checkpoint for these tests and record each result
separately. The procedure covers the normal Out1 handoff;
CutDestination remains unwired and is not tested by this lab.
Required environment
| Component | Exact version |
|---|---|
| Cyberpunk 2077 for Windows (GOG) | 2.31a (public patch 2.31) |
| WolvenKit | 8.19.0 |
| ArchiveXL | 1.27.0 |
| RED4ext | 1.30.0 |
| redscript | 0.5.31 |
Record observed versions on every run. Values printed in the template are requirements, not proof of the machine that executed it.
Establish the site before installation
Lab 4 uses the same Allen Street test site and trigger geometry as Lab 3. Its center is the outdoor recycling-station cabinet row in Watson/Kabuki at:
(-1000.02, 1497.2208, 8.3)
Before installing any Lab 4 candidate, follow Lab 3’s ordinary control-route procedure. It cites the vanilla fast-travel entity, linked marker, exact depot resources, and extraction steps. Do not use Lab 4’s own marker as the only way to find a marker that is under test.
Treat access to the route and the new cqa004 world resources as
Experimental until performed on the pinned game build. If the ordinary
route is inaccessible, record that result. Do not silently substitute a
console teleport.
Prepare clean starting save
Before installing either checkpoint:
- choose a manual save made at Allen Street with no history of any Lab 4 candidate;
- confirm the player is outside the 25-metre reach volume and can approach the cabinet row by the verified ordinary route;
- copy the complete save-slot directory into your test folder;
- record its in-game label and slot-directory name;
- confirm that this save predates the first Lab 4 install;
- preserve that original byte-for-byte for both
clean-walkandclean-replay; - derive separate active-child saves before reach and between boundaries;
- derive a separate parent-active save during the 30-second confirmation;
- preserve the completed save used by both completed-state cases.
Use a fresh working copy for each run. Preserve the same source save for
clean-walk/clean-replay, pre-reach-reload/stream-away-return, and
completed-reload/completed-reinstall. Keep separate between-boundaries
and parent-confirmation saves.
Do not obtain “clean” evidence by writing cqa004_completed = 0, removing the
mod after a prior load, or reusing a slot that saw the start checkpoint.
FactsDB, journal, phase, mappin, and world state can all survive those actions.
Build and bind the canonical candidate
Use the unmodified completed checkpoint:
candidate id: canonical
project: CQA_Lab04_HandoffPoint.cpmodproj
-
Close the game.
-
Build and install through WolvenKit.
-
Confirm only this candidate’s pair is installed:
archive\pc\mod\CQA_Lab04_HandoffPoint.archive archive\pc\mod\CQA_Lab04_HandoffPoint.archive.xl -
Confirm the archive contains all seven depot resources, including the child and both sectors reached through the block.
-
Preserve those installed files until all eight executions finish.
Preflight the ownership boundary
Before Run 1, inspect the installed .archive.xl and record:
- exactly one
quest.phasesentry, forcqa004.questphase; - no independent entry for
cqa004_boundary.questphase; - the journal, onscreen localization, and streaming block entries;
- no
cqa004error in a fresh ArchiveXL startup log.
This preflight does not prove the child executes. It proves the candidate under test has the intended one-root registration shape.
Run 1: clean walk
Restore a working copy of the original untouched save, keep the canonical candidate installed, and start a fresh execution.
Before entering the reach volume
Record all of these visible and state observations:
- Handoff Point, its phase, and
Reach the handoff point.become active once; - the handoff map pin becomes active once and points to the marker;
- the leave and confirmation objectives are not active;
- no second root or duplicate child activation appears.
The active reach objective and pin show child-owned work, but do not by themselves prove the prefab scope is correct. The trigger crossing and logs are also required.
Cross the reach boundary
Walk to the cabinet row and cross into the 25-metre reach volume. Confirm:
- the pin becomes inactive;
- the reach objective succeeds once;
Clear the handoff area.becomes active once;- the quest does not yet show the confirmation objective;
- the quest does not take its completed-save bypass.
Remain inside the 110-metre leave volume long enough to confirm the leave gate does not immediately complete.
Cross the leave boundary and observe return
Leave the 110-metre volume by ordinary movement. As soon as the leave objective succeeds, observe the child-to-parent boundary:
Wait for handoff confirmation.becomes active once;- the reach and leave sequence does not restart;
- after 30 realtime seconds, confirmation and phase succeed;
- the completion-fact write is reached before final quest success;
- the quest succeeds once.
The confirmation objective is the visible evidence that child Out1 reached
parent node 13.Out1 -> 14.Active. Preserve a derived save during this window
for Run 4 and the final completed save for Runs 6 and 7.
Copy all four fresh logs before another execution can replace them.
Run 2: pre-reach reload
Restore the derived save made after the child activated but before reaching the 25-metre volume.
- Load without changing the installed candidate.
- Confirm the reach objective and pin return once.
- Confirm leave and confirmation are not yet active.
- Enter reach, remain inside leave, then walk outside.
- Confirm the child returns once and the parent completes once.
- Retain the save name, observations, and four fresh logs.
Pass requires coherent active-child restoration. Restarting the root flow or duplicating the pin is a failure even if the quest eventually completes.
Run 3: between-boundaries reload
Restore the derived save made after reach succeeded while the player remained inside the leave volume.
- Confirm reach remains succeeded and the handoff pin remains inactive.
- Confirm leave is active and confirmation is not active.
- Wait briefly without crossing; the child must not return yet.
- Walk outside the leave volume.
- Confirm leave succeeds, confirmation activates once, and completion follows once.
- Retain the save name, observations, and four fresh logs.
Immediate completion on load is a failure unless the retained position is actually outside the authoritative leave volume.
Run 4: post-return reload
Restore the derived save made after the child returned and while the parent’s 30-second confirmation delay was active.
- Confirm the confirmation objective is active.
- Confirm reach and leave do not reactivate and no pin returns.
- Observe whether the realtime delay resumes or restarts; record the measured result rather than assuming one.
- Confirm the parent succeeds confirmation, phase, fact, and quest once.
- Retain the save name, timing observation, and four fresh logs.
Pass requires a coherent parent-owned continuation. The matrix records timing semantics; the procedure does not pre-label resume versus restart.
Run 5: stream away and return while child is active
Restore a working copy of the active-before-reach child save.
- Travel by ordinary movement beyond the finite Quest descriptor box; do not use fast travel for this case.
- Remain away long enough to collect a clear before/away/return observation.
- Return by ordinary movement to the site.
- Confirm the reach objective and pin remain coherent.
- Cross reach and leave, then confirm one child return and one parent completion.
- Retain route notes, save name, observations, and four fresh logs.
This case tests active composition across world streaming. It does not test
CutDestination and does not generalize to every prefab lifetime.
Run 6: completed reload
Restore the completed save made by Run 1 with the unchanged canonical candidate installed.
Confirm:
- Handoff Point remains completed;
- reach, leave, pin, and confirmation do not reactivate;
- the external child is not invoked again;
- fresh logs contain no
cqa004error.
This is the completed-route bypass, not a second successful playthrough.
Run 7: completed reinstall
- Close the game.
- Remove only the two installed canonical files.
- Reinstall the preserved copies of the same two files.
- Load the same completed save.
- Confirm the quest remains completed and the child does not restart.
- Confirm ArchiveXL registers the root without a separate child entry.
- Retain four fresh logs.
Do not rebuild between removal and reinstall. A changed archive is a new candidate and invalidates the shared record.
Run 8: clean replay
Restore a fresh working copy of the original untouched pre-install save while the canonical candidate remains installed.
Repeat the ordinary walk and record the full sequence:
root first-run route
-> child reach/pin
-> child leave
-> child Out1
-> parent confirmation
-> parent completion
Every transition must occur once with the same installed files and no
cqa004 errors. This verifies reproducibility from a second clean derivation;
it does not erase the need for the six boundary-specific runs.
Retain four logs per execution
After each of the eight runs, copy these files into a run-specific private test folder before starting the next execution:
red4ext\plugins\ArchiveXL\ArchiveXL.log
red4ext\logs\red4ext.log
red4ext\logs\game.log
r6\logs\redscript_rCURRENT.log
Keep the copied logs with that run’s test notes.
Search all four for cqa004, the child depot path, NodeRef errors, streaming
errors, journal/localization errors, and framework failures. “No visible
crash” is not an adequate log result.
Common test failures
| Observation | Classification and next check |
|---|---|
| Parent registered, child path error in log | Failed external resolution; verify packed path and soft phaseResource |
| Child appears to run twice | Failed lifecycle; verify clean save, one root registration, and no duplicate candidate |
| Trigger refs fail only from child | Failed candidate; inspect root prefab, child/instance empty lists, full world binding, and hashes before changing declarations |
| Leave succeeds but confirmation never activates | Failed handoff; inspect child output and parent 13.Out1 -> 14.Active |
| Reload before reach restarts journal/pin | Failed active-child recovery, even if later completion succeeds |
| Post-return reload restarts child | Failed owner transition; retain evidence before editing the graph |
| Completed reinstall starts child again | Failed one-shot persistence or candidate identity |
| A cut path is untested | Expected scope boundary; do not mark cut runtime-proven |
| One run is missing its logs | Repeat that run if you need to diagnose or report it reliably |
Lab sequence: All labs · Previous: Author Handoff Point in WolvenKit · Next lab: Lab 5: First Contact.
Conditions and gates
A condition answers a question about game state. A gate decides when that question is asked and what execution signal leaves afterward. Keeping those jobs separate is the foundation of reliable quest control flow.
“Continue when the player has arrived” can mean at least three different things:
- sample the arrival state now and choose
TrueorFalse; - hold this execution path until the arrival state is fulfilled;
- combine arrival with other predicates before either of those graph nodes acts.
Those are different native resource shapes. A visually similar graph can therefore have different runtime behavior.
Three layers, three questions
Trace a gate from the outside in:
execution signal
|
v
graph node: evaluate now or wait?
|
v
condition object: which state is being tested?
|
v
leaf payloads and Boolean composition
Then trace outward again through named sockets. The three layers answer different questions:
| Layer | Native examples | Question |
|---|---|---|
| Evaluation schedule | questConditionNodeDefinition, questPauseConditionNodeDefinition | Is the predicate sampled on entry, or does this path wait? |
| Predicate | questFactsDBCondition, questTriggerCondition, questJournalCondition | What piece of game state counts as fulfilled? |
| Predicate composition | questLogicalCondition containing child conditions | Must all, any, or some other combination of child predicates be fulfilled? |
Graph-level logical nodes are a fourth, separate concern: they receive
execution signals through sockets. A questLogicalCondition does not merge
graph edges, and a graph-level AND or XOR does not inspect facts by itself.
Using the same English words for both layers is convenient, but the serialized
types reveal which contract actually applies.
Choose the native shape from the behavior
| Intended behavior | Starting shape | Essential caution |
|---|---|---|
| Decide between two routes using current state | Immediate condition node | False is an output route, not a request to wait |
| Continue this route after a predicate is fulfilled | Pause condition node | Do not assume how it polls, saves, cancels, or re-arms |
| Require several state predicates together | Logical condition tree beneath either node | This combines truth values, not execution signals |
| Prevent a one-shot effect from running again | Immediate fact guard plus an explicit persistent fact write | The save may already contain either value |
| Watch for failure while other work continues | A separately activated waiting path | Starting a listener does not define how it is stopped |
| Accept one of several arriving graph signals | Graph topology, not a nested condition tree | Winner, cancellation, and tie behavior require their own evidence |
The table is a design router, not a copy-paste recipe. Every row still needs an explicit entry edge, exit edge, lifecycle owner, and evidence plan.
Read this section in order
| Chapter | Question it answers |
|---|---|
| Immediate branches and waiting gates | When does a condition run, and which sockets continue the graph? |
| Condition payloads | How do fact, trigger, distance, journal, and time predicates represent state? |
| Boolean condition trees | How are leaf predicates nested under AND or OR, and why is there no generic NOT recipe? |
| Signal flow | How do graph-level AND, XOR, Hub, and Switch shapes route execution signals? |
| Delays and persistence | Which clock and save-state questions must a delayed route answer? |
| Parallel monitors and cancellation | How can a side listener remain harmless or be explicitly cut? |
| Condition-family catalog | Which additional native condition families were observed, and what still needs research? |
| Lab 2: Signal Race | How do immediate selection, parallel waits, a Boolean AND, and XOR-shaped convergence fit in one project? |
| Author Signal Race | Which native properties and 22 socket edges reproduce the supplied graph? |
| Test both routes | How are two source variants, clean saves, hashes, logs, and outcomes kept distinct? |
Start with the node schedule. A perfectly authored fact comparison still behaves incorrectly if it sits under the wrong surrounding graph node.
Inspect vanilla conditions
Use a disposable WolvenKit project for vanilla research:
- In the Asset Browser, locate the exact depot path named by a chapter.
- Open it without adding it to a distributable project, or add it only to a separate local inspection project.
- Record the graph-local node ID, concrete node class,
conditionpayload, socket names, and focused connected edges. - Record the game version, WolvenKit version, extraction date, and optionally a local hash.
- Keep the extracted
.questphaseand any complete CR2W-JSON serialization out of the book and downloadable examples.
Use the depot path to locate a reference. Adapt only the fields you need; vanilla facts, NodeRefs, and journal paths belong to the original quest.
State is not an event sequence
Many predicates describe state: a fact has a value, a journal entry has a
state, or an actor is inside an area. Even when a payload enum contains words
such as Entered or Exited, the enum name alone does not establish its exact
edge timing, persistence, or replay behavior.
Ordered behavior comes from explicit execution edges:
wait for A
-> wait for B
-> perform C
Putting A and B in one AND tree asks whether both predicates are fulfilled for one condition evaluation. It does not record that A happened first. Starting two waiters in parallel likewise does not establish which will emit first or whether the losing path will stop.
Test timing-dependent behavior
When your quest depends on timing, test pause, loading, time skip, fast travel, and save/load. For parallel signals, test both arrival orders and simultaneous arrival. For reusable waits, test interruption and re-entry.
Immediate branches and waiting gates
questConditionNodeDefinition and questPauseConditionNodeDefinition both own
a condition handle. Their surrounding graph contracts are different:
immediate waiting
In In
| |
evaluate now hold this path
+-- True -> route A |
`-- False -> route B `-- Out -> continue when fulfilled
Choosing between them is a behavior decision, not an editor-layout choice.
Vanilla references
These focused base-game resources are useful comparisons. Extract them from your own installation; they are references, not files to copy into the project.
base\open_world\street_stories\westbrook\japantown\sts_wbr_jpn_03\phase\sts_wbr_jpn_03_streetstory.questphase
Immediate condition: answer on entry
Execution entering questConditionNodeDefinition causes its predicate to
select a route. The focused vanilla shape has:
| Part | Role |
|---|---|
condition | Handle to the leaf or logical predicate |
In | Ordinary execution entry |
True | Route selected when the predicate is fulfilled |
False | Route selected when the predicate is not fulfilled |
CutDestination | Separate interruption target; not another Boolean result |
The decisive property is the node class. When the predicate is false, the
node follows False; it does not remain active hoping that the value will
change.
A startup prerequisite can therefore reject or bypass work immediately:
In
-> is prerequisite ready?
True -> begin activity
False -> unavailable route or termination
If the design instead says “start whenever the prerequisite becomes ready,”
this graph is incomplete. The False route needs some explicit retry owner,
or the predicate belongs under a waiting node.
Pause condition: retain one execution path
questPauseConditionNodeDefinition receives an execution signal and delays
that path until its predicate is fulfilled. Its focused observed shape has:
| Part | Role |
|---|---|
condition | Handle to the leaf or logical predicate |
In | Starts this wait |
Out | Continues after fulfillment |
CutDestination | Target for an explicitly wired cut path |
There is no ordinary False continuation in this shape. A false predicate
means that this execution path has not yet produced Out.
That makes the node suitable for state gates such as:
- a fact reaching an authored value;
- the player satisfying a trigger predicate;
- a journal entry reaching a state;
- a real-time or game-time delay completing.
The nested payload defines fulfilled. Condition payloads shows the focused property shapes. A Boolean condition tree can combine several payloads beneath either surrounding node.
The same predicate does not mean the same behavior
Suppose signal_ready currently equals 0 and later becomes 1.
questConditionNodeDefinition(signal_ready == 1)
selects False when the signal arrives at that moment. It has already made
its decision.
questPauseConditionNodeDefinition(signal_ready == 1)
owns a waiting path whose intended release condition is the later fulfilled comparison. Do not turn that explanation into unsupported scheduling claims: the retained structure alone does not say how often the engine revisits the predicate, what happens during a load screen, or whether a terminated phase cleans up an active waiter.
Author the node in WolvenKit
Use WolvenKit 8.19.0 and a mod-owned .questphase. The following procedure
describes the native resource shape; it does not require a generator or a
published vanilla asset.
- Decide the behavior in plain language: branch now or wait until.
- Add
questConditionNodeDefinitionfor the first behavior, orquestPauseConditionNodeDefinitionfor the second. - In the node’s
conditionhandle, create the wrapper that owns the intended state domain. For a fact comparison, that isquestFactsDBConditionwith aquestVarComparison_ConditionTypebeneathtype. - Set every decisive payload property. Do not leave a default fact name, journal path, NodeRef, comparison, or threshold unexplained.
- Show unused sockets if necessary. Connect
In, then bothTrueandFalsefor an immediate branch, or connectInandOutfor a waiting gate. - Leave
CutDestinationunwired unless the activity has a designed and tested interruption owner. - Save the resource, reopen it, and verify the concrete node class, payload handle, socket names, and connected destination IDs.
For a structural record, serialize and round-trip the mod-owned resource, then compare the focused node and edge set. A successful save is not runtime proof.
One-shot activation is an immediate branch
A one-shot side effect needs persistent control state and an already-done route:
In
-> started == 0?
True -> set started = 1 -> perform one-shot side effect
False -> bypass the side effect
Facts are signed integers, even when 0 and 1 are used by convention. The
fact writer is a separate graph operation; the comparison does not change the
fact.
The book’s mod-owned First Signal checkpoint uses
cqa001_completed == 0 in this shape and is Structurally validated with
WolvenKit 8.19.0. Its clean-save runtime evidence remains
Experimental — pending.
Place the write according to the invariant you need. If started means “do
not send the opening presentation twice,” write it before or immediately with
that presentation. If completed means “the whole activity finished,” write
it only on the completed route. When restart and completion differ, use
separate facts instead of overloading one value.
Writing a start guard early can suppress a retry after interruption; writing it late can repeat the guarded side effect. That is a lifecycle trade-off to test, not an ordering rule hidden inside the condition node.
Re-entry safety is Experimental. Test repeated entry, save/load, and reinstallation over a completed save.
A waiting monitor is still active work
A graph output can start a normal activity and a pause condition in parallel:
start
+-> main activity
`-> wait for failure fact -> fail optional objective
This is a monitor-shaped topology. It does not imply that success on the main
path cancels the listener. The vanilla street-story resource cited above
contains a parallel stealth-failure listener and later reads the stored result,
but contains no CutSource for that listener. That is Observed in vanilla
evidence for the shape, not automatic cleanup.
For every waiting path, name:
- who starts it;
- which predicate releases it;
- what output consumes the release signal;
- whether another route can make it irrelevant;
- how it is explicitly cut, completed, or made harmless;
- which save/reload and re-entry cases must be exercised.
An unwired CutDestination does not answer those questions.
Save-backed facts change the first entry
A fact used by either condition node may already exist in the save. Installing a new archive does not guarantee its authored comparison begins at zero.
Test at least these distinct cases for a one-shot or waiting fact gate:
- an untouched save made before the mod was installed;
- a save while the waiter is active;
- a save after the releasing fact changed;
- a completed save after reinstalling the identical build;
- a replay from the original untouched save.
Record the archive and loose-file hashes with the save identity and logs. A console fact edit is useful diagnosis, but it does not clear journal state, active graph state, or other save-backed dependencies and therefore is not a clean-save result.
Diagnose the schedule before the payload
When a gate takes the wrong route or never releases:
- Confirm the concrete node class: immediate Condition versus Pause Condition.
- Trace the edge into
Inand every expected outgoing socket by node ID. - Inspect the concrete payload and its comparison instead of relying on the node caption.
- Check the fact, journal path, NodeRef, or timer fields for the test save and installed build.
- Determine whether the path was started once, more than once, or never.
- Check whether a competing route completed without stopping this waiter.
- Repeat the first-entry route from the original untouched save before changing the resource again.
That order separates a scheduling mistake from a malformed predicate, stale save state, missing dependency, and unowned cleanup.
Condition payloads
The surrounding graph node decides when a condition matters. Its condition object decides what counts as fulfilled. In serialized form, many predicates have two payload layers:
quest condition node
└── condition domain wrapper
└── type concrete comparison or event payload
For example, questFactsDBCondition owns a
questVarComparison_ConditionType, while questTimeCondition owns a
real-time or game-time delay type. Do not replace one wrapper while leaving an
incompatible nested type handle behind.
Read Immediate branches and waiting gates before this chapter. Both graph-node classes carry the same base-condition handle, but a concrete payload still needs evidence for its intended use. The retained fact comparison appears beneath both schedules; the delay recipes below appear beneath Pause nodes.
Fact comparison
The focused shape is:
questFactsDBCondition
└── type questVarComparison_ConditionType
├── factName string (`CString` in WolvenKit's RED type)
├── value signed integer
└── comparisonType comparison enum
The comparison enum provides:
comparisonType | Predicate |
|---|---|
Equal | fact value equals authored value |
NotEqual | fact value does not equal authored value |
Greater | fact value is greater than authored value |
GreaterOrEqual | fact value is at least authored value |
Less | fact value is less than authored value |
LessOrEqual | fact value is at most authored value |
Facts are integers. Authors often use 0 and 1 as a Boolean convention, but
the resource does not turn the fact into a Boolean type. These predicates can
express counters and stage values as well:
signal_ready == 1
guards_remaining <= 0
quest_stage >= 3
Choose the comparator that states the invariant. Equal 1 and Greater 0
are not interchangeable when another system can write 2.
The same vanilla street-story phase uses
jpn_03_stealth_fail == 1 beneath both a Pause node and an immediate
Condition node:
base\open_world\street_stories\westbrook\japantown\sts_wbr_jpn_03\phase\sts_wbr_jpn_03_streetstory.questphase
That is Observed in vanilla evidence that the fact payload is reusable under both schedules. It is not evidence that a fact is automatically reset when either node completes.
Author a fact predicate
In a mod-owned node’s condition property:
- Create a
questFactsDBConditionhandle. - Create
questVarComparison_ConditionTypebeneath itstypeproperty. - Set the exact
factName, signed integervalue, andcomparisonType. - Reopen the resource and verify both handles are non-null and retain their concrete classes.
- Test from a save whose prior fact state is known.
NotEqual is payload-specific negation. It does not prove that a generic NOT
condition exists. Boolean condition trees develops that
boundary.
Trigger condition
questTriggerCondition carries its decisive fields directly:
| Property | Role |
|---|---|
type | Trigger predicate such as IsInside or IsOutside |
triggerAreaRef | NodeRef of the authored trigger area |
activatorRef | Entity reference used when an explicit activator is required |
isPlayerActivator | Selects the player as activator in the observed player form |
An Observed in vanilla player gate appears at root Pause node 20 in:
base\quest\main_quests\part1\q108\phases\q108_06b_tower_mainframe.questphase
It uses type: IsInside, the #q108_tr_mainframe triggerAreaRef, and
isPlayerActivator: 1. The same phase also contains a nested AND tree that
names several non-player activators and requires them inside a trigger.
This predicate crosses into world ownership. #q108_tr_mainframe resolves
because that quest’s streamed resources provide it. Reusing the text in a new
quest does not create a trigger. A mod-owned condition needs a NodeRef that
resolves to its own loaded trigger area at the time the gate is active.
Enum names such as Entered, Exited, IsInside, and IsOutside must not be
used as proof of exact temporal behavior. The retained serialization does not
establish whether a form is edge-triggered or state-recovering through load,
streaming changes, teleportation, or activation while the player is already
inside. Treat those cases as Experimental until tested.
Author a trigger predicate
- Establish ownership of the mod’s trigger and its NodeRef before editing the questphase.
- Create
questTriggerConditionbeneath the graph node’sconditionhandle. - Set
triggerAreaRefand the intendedtypeexplicitly. - For the player form, set
isPlayerActivatordeliberately and inspect the resultingactivatorRef; for another actor, begin from a separately verified actor-reference shape. - Test activation from outside, inside, after streaming transition, and after save/load before claiming state or event semantics.
IsOutside is another payload-specific way to express a negative predicate.
It is not a universal logical NOT wrapper.
Distance comparison
A distance predicate separates the comparison from its two operands:
questDistanceCondition
└── type questDistanceComparison_ConditionType
├── distanceDefinition1 questObjectDistance handle
├── distanceDefinition2 questValueDistance handle
└── comparisonType comparison enum
Two focused operand types are:
| Type | Decisive properties |
|---|---|
questObjectDistance | entityRef, nodeRef2 |
questValueDistance | distanceValue |
Root Pause node 251 provides an Observed in vanilla object-versus-value
example in:
base\quest\side_quests\sq031\phases\sq031_rogue.questphase
Its first operand resolves the local player and #emmerick; its second operand
is distanceValue: 4; its comparison is LessOrEqual.
Call 4 a numeric distance threshold. The retained resource does not prove
that the authored unit is exactly one metre, which reference points on the two
entities are measured, or how streaming and teleportation affect evaluation.
Before authoring a new distance gate, state both operands in plain language:
distance(local player, contact NodeRef) <= threshold
Then verify the nested concrete types after saving. A null operand handle, wrong NodeRef, wrong local-player selector, or unintended numeric value changes the predicate even when the graph caption still says “distance.”
Journal predicates
Three similarly named nested payloads represent different questions:
Concrete type beneath questJournalCondition | Decisive fields | Question |
|---|---|---|
questJournalEntryState_ConditionType | path, engine state, inverted | Is the entry in an authored lifecycle state, optionally inverted? |
questJournalEntry_ConditionType | path, user state | Is the entry in an authored user-facing state? |
questJournalEntryVisited_ConditionType | path, visited | Does the entry have the authored visited flag? |
Every path is a typed gameJournalPath, not only a slash-separated string.
Its realPath, className, and fileEntryIndex must identify the supplied
journal entry together.
An immediate state example is Condition node 195 in:
base\quest\side_quests\sq011\phases\sq011_concert.questphase
It checks whether a phone choice group is Active with
questJournalEntryState_ConditionType. The typed path records the exact leaf
class and file-entry index.
The retained corpus’s single visited payload appears beneath Pause node 127
in:
base\open_world\street_stories\heywood\vista_del_rey\sts_hey_rey_09\phases\sts_hey_rey_09_openworld.questphase
It addresses phone message
contacts/8ug8ear/sts_hey_rey_09/warning4 with
questJournalEntryVisited_ConditionType and visited: 1.
That one example does not make “journal visited” a generic signal for reading an inventory shard, computer page, email, or unrelated UI item. Use the entry family and event source that actually own the interaction.
Author a journal predicate
- Identify the exact journal entry and the state dimension being tested: lifecycle, user state, or visited.
- Create
questJournalCondition, then choose the matching concrete nested type. - Populate the full typed path. Verify
classNameagainst the leaf resource, not against the surrounding quest or folder. - Set
state,visited, orinvertedonly where that concrete type owns the property. - Test from separate untouched, active, visited, and completed saves as the design requires.
The inverted field belongs to the entry-state payload. It is not a generic
property available on every condition family.
Time conditions
questTimeCondition wraps a concrete time payload. Two common retained forms
are:
Concrete type | Fields | Retained count |
|---|---|---|
questRealtimeDelay_ConditionType | hours, minutes, seconds, miliseconds | 146 |
questGameTimeDelay_ConditionType | days, hours, minutes, seconds | 19 |
The native real-time property is spelled miliseconds. Preserve that exact
property name when reviewing serialized data; “correcting” it creates a
different, unknown field rather than configuring the resource.
Focused Observed in vanilla examples include:
- a two-second real-time delay at nested Pause node
71inbase\open_world\street_stories\westbrook\japantown\sts_wbr_jpn_03\phase\sts_wbr_jpn_03_streetstory.questphase; - a 23-hour game-time delay at node
146inbase\quest\side_quests\sq011\phases\sq011_concert.questphase; - a three-day delay at node
61and a five-hour delay at node87inbase\quest\side_quests\sq011\phases\sq011_follow_up.questphase.
Tick-delay and time-period payloads are also Observed in vanilla. Inspect a matching use before adapting those less common forms.
A delay owns only its gate. The downstream graph owns the call, message, journal update, or other presentation that follows. A 23-hour condition in a vanilla contact flow does not mean the delay itself schedules phone UI.
Author and test a delay
- For the delay recipes covered here, put
questTimeConditionbeneathquestPauseConditionNodeDefinition. The retained immediate time condition uses a differentquestTimePeriod_ConditionTypeinside an AND tree; that snapshot time-window shape is outside this recipe. - Create
questTimeConditionand the intended real-time or game-time nested type. - Set every time component explicitly, including zero-valued components.
- Verify the exact concrete type and native property spelling after save and round-trip.
- Record normal-play elapsed behavior separately from pause menu, time skip, fast travel, save/load, restart, and loading-screen cases.
The cited files establish resource shapes, not clock policy. Every behavior in step 5 is Experimental until the exact mod-owned build is exercised and the observation is retained.
Validate the payload before runtime
For any condition family, record this focused resource view:
| Check | What to capture |
|---|---|
| Surrounding node | Concrete immediate or pause node class and graph-local ID |
| Wrapper | Concrete class stored by the node’s condition handle |
| Nested payload | Concrete type class, if that wrapper owns one |
| Decisive properties | Comparison, value, path, NodeRef, activator, or time fields |
| Socket route | Entry socket and every connected result socket |
| External owner | Fact writer, journal resource, streamed NodeRef, actor, or clock assumption |
A non-null handle and successful WolvenKit save are necessary structural checks. They do not prove that an external reference resolves, that save state starts clean, or that the runtime schedule matches the design.
Boolean condition trees
questLogicalCondition combines child condition objects into one predicate.
It is embedded beneath an immediate or waiting graph node; it is not itself a
graph node and has no execution sockets.
questPauseConditionNodeDefinition
└── condition questLogicalCondition
├── operation AND
└── conditions
├── questTriggerCondition
└── questLogicalCondition
├── operation OR
└── conditions ...
The surrounding node still decides the schedule:
- beneath
questConditionNodeDefinition, the combined predicate chooses an immediateTrueorFalseroute; - beneath
questPauseConditionNodeDefinition, the combined predicate defines when the waiting path may continue throughOut.
Review Immediate branches and waiting gates and Condition payloads before constructing a nested tree.
Condition tree versus signal-flow topology
Two native layers use similar names:
| Predicate layer | Execution-signal layer |
|---|---|
questLogicalCondition | graph nodes such as questLogicalAndNodeDefinition |
Owns an operation and child conditions | Owns graph sockets and receives edges |
| Combines truth values | Combines or routes arriving execution signals |
Nested in one node’s condition handle | Listed as a graph node with a numeric id |
This distinction prevents a common category error. A predicate AND can ask
whether the player is inside an area and a fact is set. It cannot wait for
two independent execution branches to arrive. Conversely, a graph-level join
does not read those state predicates merely because its type name contains
And.
When inspecting CR2W-JSON, ask where the object lives. An object under
condition.Data.conditions is predicate composition. An object in the
graph’s nodes collection with sockets and an id is signal topology.
A nested vanilla tree
Pause node 28 in this resource provides a compact Observed in vanilla
example:
base\open_world\minor_activities\watson\northside\ma_wat_nid_15\ma_wat_nid_15_phase.questphase
Its focused predicate is:
AND(
player IsInside #ma_wat_nid_15_tr_area_vision,
OR(
signpost_start > 0,
signpost_combat > 0,
signpost_ended > 0
)
)
The outer AND owns two child handles: one trigger condition and one nested logical condition. The inner OR owns three fact conditions. Parentheses are therefore resource ownership, not visual decoration:
inside AND (start OR combat OR ended)
is not the same tree as:
(inside AND start) OR combat OR ended
Record nesting explicitly whenever prose uses both AND and OR.
AND: every child predicate
Use an AND tree when the authored predicate requires every child state together:
AND(
prerequisite_fact > 0,
player IsInside #activity_area
)
The surrounding graph node decides whether that combined truth is sampled now or waited upon. AND does not establish the order in which the two states became true. If the quest requires “first set the prerequisite, then enter the area,” encode that sequence with graph edges and separate gates.
The q108 tower-mainframe phase contains another Observed in vanilla AND tree requiring the player and three named actors inside a trigger:
base\quest\main_quests\part1\q108\phases\q108_06b_tower_mainframe.questphase
That structure shows heterogeneous activator references beneath one logical tree. It does not prove which child is inspected first, whether inspection short-circuits, or how actor streaming affects a new mod-owned version.
OR: any represented alternative
Use an OR tree to represent alternative leaf predicates within one condition:
OR(
quest_stage == 2,
quest_stage == 3,
quest_completed > 0
)
OR does not report which child was responsible. The surrounding condition node produces only its own result socket. If downstream work needs to know the winning reason, preserve that information in separate immediate branches or in authored state rather than expecting the OR payload to emit a named case.
Do not rely on child-array order for priority. The retained structures support membership and nesting; they do not establish short-circuit order or side effects during evaluation.
XOR is a bounded observation, not a general recipe
The only retained logical-condition XOR is a four-child tree beneath Pause
node 127 in:
base\open_world\street_stories\heywood\vista_del_rey\sts_hey_rey_09\phases\sts_hey_rey_09_openworld.questphase
Its children are two scan predicates, one journal-visited predicate, and one fact predicate. This is Observed in vanilla evidence that the serialized four-child shape exists. It is not enough to claim whether multi-child XOR means exactly one fulfilled child, parity, a first-changing child, or another runtime policy. It also says nothing about cancellation because a condition tree owns no graph listener sockets to cancel.
For a new quest, keep XOR Experimental until a focused truth-table test covers every relevant child combination, including more than one child fulfilled together and state restored from a save.
There is no generic NOT condition
The logical-operation enum in the inspected WolvenKit classes contains AND, OR, XOR, NAND, NOR, and NXOR. It does not contain NOT, and the retained corpus contains no standalone generic negation node.
Express negation only through a payload that owns a verified negative form:
| Domain | Focused negative form | Boundary |
|---|---|---|
| Fact comparison | comparisonType: NotEqual | Negates that integer equality, not an arbitrary subtree |
| Trigger state | type: IsOutside | Describes that trigger predicate; exact transition timing still needs tests |
| Journal lifecycle state | questJournalEntryState_ConditionType.inverted: 1 | Belongs to this concrete journal payload only |
For example:
AND(
alarm_state != 1,
player IsOutside #restricted_area
)
uses two independently supported leaf forms. It does not prove a hypothetical
NOT(AND(...)) resource shape.
NAND, NOR, and NXOR should not be offered as substitutes merely because their enum values exist. With no retained vanilla instance and no focused runtime acceptance, their precise multi-child behavior remains Experimental.
Author a nested tree in WolvenKit
Use a mod-owned .questphase in WolvenKit 8.19.0:
- Choose the surrounding immediate or pause node from the intended schedule.
- Set that node’s
conditionhandle toquestLogicalCondition. - Set
operationto the supported operation required by the design; begin with AND or OR. - Add one handle per child to
conditions. Each child must contain a concrete condition wrapper such asquestFactsDBConditionorquestTriggerCondition. - To nest another group, store another
questLogicalConditionas that child handle’s concrete object, set its operation, and populate its ownconditionsarray. - Inspect every leaf’s decisive properties and every nested handle before leaving the property editor.
- Save, reopen, and verify the tree shape independently of the graph layout.
For the observed MA_WAT_NID_15 shape, the review record should be small and deterministic:
| Depth | Object | Decisive value |
|---|---|---|
| 0 | questLogicalCondition | operation: AND, two child handles |
| 1 | questTriggerCondition | player IsInside the named trigger NodeRef |
| 1 | questLogicalCondition | operation: OR, three child handles |
| 2 | three questFactsDBCondition objects | named facts, each Greater 0 |
Do not paste the complete vanilla resource into notes or a chapter. This focused tree plus the depot path is enough to reproduce the inspection.
Structural checks
A Boolean tree is Structurally validated only after the mod-owned resource passes checks such as:
- the surrounding node’s
conditionhandle resolves; - every item in
conditionsis a non-null handle with the intended concrete class; - nested group boundaries and child counts survive CR2W round-trip;
- every fact name, comparison, typed journal path, and NodeRef is exact;
- the graph node retains the expected
Inand result sockets; - no graph-level logical node was substituted for an embedded condition tree.
Structural validation still does not prove runtime timing. For a waiting tree, exercise child states becoming true in different orders, states already true on activation, save/load during the wait, and exit while it is active. For an immediate tree, exercise each result route and combinations where several children are true together. Until those cases are retained against the exact build, the behavior beyond the observed resource shape is Experimental.
Return to the Conditions and gates overview when deciding whether the next requirement belongs to predicate composition or execution-signal topology.
Signal flow: joins, races, hubs, and switches
Graph-level logical nodes combine execution signals. They are not the
Boolean condition trees stored inside a Condition or Pause Condition node.
Keeping those layers separate prevents one of the easiest questphase mistakes:
treating a socket arrival as though it were a true value.
Use Boolean condition trees when several observations must be evaluated as one predicate. Use the nodes in this chapter when several already-running graph routes must meet, compete, or choose an output.
A connection is an edge
A graph node owns sockets. Each socket has a name, a socket type, and a
connections array. A connection points weakly to its source and destination
sockets.
CR2W-JSON often exposes the same connection handle from both endpoints. That does not create two edges. Count the connection object once, then resolve both socket handles.
The native socket kinds relevant here are:
| Socket type | Role |
|---|---|
Input | Accepts an ordinary execution signal |
Output | Emits an ordinary execution signal |
CutSource | Connects a cut-control route to a target |
CutDestination | Receives an explicit cut connection |
Socket arity and connection count are different. One Out socket can hold
several outgoing connections, and one In socket can be the destination of
several edges. An ordinary node output can therefore fan out without a Hub.
What vanilla actually contains
The retained corpus has these graph-level nodes:
| Node type | Count | Serialized ordinary-socket shapes |
|---|---|---|
questLogicalAndNodeDefinition | 47 | 2–11 inputs, one output |
questLogicalXorNodeDefinition | 51 | 2–4 inputs, one output |
questLogicalHubNodeDefinition | 95 | 1→1/2/3 and 2/3→1 |
questSwitchNodeDefinition | 1 | one In, three cases, one Otherwise |
For all 193 retained AND, XOR, and Hub nodes, inputSocketCount and
outputSocketCount match the serialized ordinary socket arity. Each also owns
one CutDestination socket. The separate Switch is the table’s 194th node and
is not included in that 193-node statement.
That inventory supports property and topology claims. It does not, by itself, prove what happens after the first output, after a save/reload, or when inputs arrive on the same frame.
AND: join-shaped convergence
questLogicalAndNodeDefinition carries:
| Property | Meaning established by structure |
|---|---|
inputSocketCount | Number of ordinary input sockets |
outputSocketCount | Number of ordinary output sockets |
sockets | The actual named sockets and their connections |
Observed in vanilla: root node 38 in
base\quest\main_quests\part1\q108\phases\q108_06b_tower_mainframe.questphase
has four inputs and one output. A Pause Condition and three Move Puppet routes
converge on its four inputs, and Out1 continues to another Pause Condition.
This is safely described as a four-way, join-shaped topology. Raw structure alone does not prove that the node remembers every earlier arrival, fires only once, resets, or restores partial arrivals after loading a save. If your design depends on those details, make each one an explicit acceptance case.
XOR: race-shaped convergence
Observed in vanilla: root XOR node 17 in
base\open_world\street_stories\watson\northside_industrial_district\sts_wat_nid_03\phases\sts_wat_nid_03_gameplay.questphase
receives three independent wait routes:
- security system is in combat;
- an enemy attacks the player;
- the security system is alerted.
Its single output continues to a fact setter. This is a strong example of race-shaped convergence.
It is not evidence that XOR cancels the two losing listeners. Those waits have
no CutSource wiring in this phase. It also does not prove deterministic
resolution for simultaneous arrivals or that a later arrival cannot emit
again. Treat all three claims as Experimental.
Lab 2 uses a two-input XOR-shaped convergence because that exact resource can be structurally inspected and given a focused runtime matrix. See Lab 2: Signal Race.
Hub: a structural routing node
Do not teach Hub as “the fan-out node.” Vanilla uses it in several shapes:
- Observed in vanilla: root Hub 70 in the Q108 phase above has one input and three outputs.
- Observed in vanilla: root Hub 302 in
base\open_world\minor_activities\watson\northside\ma_wat_nid_15\ma_wat_nid_15_phase.questphasehas two inputs and one output. ItsOut1itself has two outgoing edges. - In the same phase, Hub 195 is formally one-input/one-output, while two edges
target that single
In1socket.
These resources prove split- and merge-shaped wiring. They do not establish whether each input is passed through independently, whether a multi-input Hub waits, whether one arrival broadcasts to all outputs, or which edge runs first. Use a Hub only after the behavior you need has its own runtime test. For a simple split, an ordinary output with several connections is the smaller structure.
Switch: conditions mapped to case sockets
questSwitchNodeDefinition owns a behaviour and a list of
questConditionItem values. Each item combines a condition handle with a
numeric socketId. The corresponding output is named Case<socketId>.
The sole Switch in the retained corpus is nested beneath phase node 252 in:
base\open_world\minor_activities\watson\northside\
ma_wat_nid_15\ma_wat_nid_15_phase.questphase
Observed in vanilla: it uses First_Fulfilled, three fact conditions, one
case output for each recorded socket ID, and an unconnected Otherwise
socket.
| Serialized part | Observed relationship |
|---|---|
conditions[n].socketId | Numeric identity for that condition’s case |
Case<socketId> | Output socket associated with the item |
In | Ordinary input that reaches the switch |
Otherwise | Additional output present in the retained shape |
The retained file does not establish evaluation order, short-circuiting,
continuous monitoring, exact Otherwise behavior, or the meaning of
All_Fulfilled. Those behaviors remain Experimental.
Author safely in WolvenKit
- Decide whether you are combining predicates or execution routes. If they are predicates, return to Boolean condition trees.
- Add the graph node with the smallest required arity.
- Set
inputSocketCountandoutputSocketCountbefore wiring it. - Confirm that the visible ordinary sockets match those counts.
- Connect each producer to the intended named input and the continuation to the intended output.
- Save, reopen, and inspect both endpoints of every edge. Do not count reciprocal handle references as separate connections.
- Export a focused review JSON and verify node IDs, socket names, arity, and resolved endpoints.
- Test every arrival order, then repeat the relevant cases across save/load and clean-save replay.
Node IDs are local to a phase graph. A nested phase graph can reuse an ID from its parent, so record the graph scope whenever you cite or audit a node.
Design checklist
Before shipping a join, race, Hub, or Switch, answer these questions with evidence rather than names:
- Which route starts each input producer?
- Can two producers finish together?
- What records the winner or completed prerequisites durably?
- Can a losing listener still write journal state later?
- What stops, cuts, or makes that late write harmless?
- What happens when saving with only some inputs satisfied?
- Can the containing phase be entered again?
- Does the completed-save route bypass every one-shot side effect?
Test any timing-dependent route in game, including both input orders and reload.
Delays, facts, and persistence boundaries
A delay in a questphase is a condition payload, normally held by a Pause Condition node. It gates the route that entered it; it does not schedule a complete quest action by itself. The nodes after the delay still own the journal update, fact write, scene start, or other effect.
This distinction matters when saves, time skips, and re-entry are involved. The serialized resource describes the delay and its wiring, while only runtime tests can establish how its active state survives interruption.
Pinned acceptance environment
| Component | Version |
|---|---|
| Cyberpunk 2077 for Windows | 2.31a (GOG) |
| WolvenKit | 8.19.0 |
| ArchiveXL | 1.27.0 |
| RED4ext | 1.30.0 |
| redscript | 0.5.31 |
Runtime test date: not yet recorded.
The book-owned Lab 2 CR2W resources are Structurally validated by cooking
and serializing them back with WolvenKit 8.19.0. The other values pin the
environment required by the future runtime acceptance run; they have not yet
been exercised together for Lab 2. Timing and reload behavior therefore remain
Experimental until the dedicated record passes.
Realtime delay payload
questTimeCondition wraps a concrete time condition. The realtime form is
questRealtimeDelay_ConditionType:
| Property | Role |
|---|---|
hours | Whole realtime hours |
minutes | Whole realtime minutes |
seconds | Whole realtime seconds |
miliseconds | Millisecond remainder; the native field is misspelled |
Keep the spelling miliseconds in the native property tree. Correcting it to
English creates the wrong field in raw representations.
Observed in vanilla: nested Pause Condition node 71 in
base\open_world\street_stories\westbrook\japantown\sts_wbr_jpn_03\phase\sts_wbr_jpn_03_streetstory.questphase
holds a two-second realtime delay.
Lab 2 uses 30- and 120-second realtime waits so the intermediate objective state is observable and a tester can make a mid-flow save. The WolvenKit round-trip proves their resource shape; it does not yet prove what the clock does in a pause menu, loading screen, or save/reload.
Game-time delay payload
questGameTimeDelay_ConditionType stores game-world duration fields:
| Property | Role |
|---|---|
days | Whole game-time days |
hours | Whole game-time hours |
minutes | Whole game-time minutes |
seconds | Whole game-time seconds |
Observed in vanilla: the following depot resources contain long game-time waits:
| Depot path | Observed payload |
|---|---|
base\quest\side_quests\sq011\phases\sq011_concert.questphase | 23 hours |
base\quest\side_quests\sq011\phases\sq011_follow_up.questphase | 3 days |
| same follow-up phase | 5 hours |
Those observations establish the payloads, not every clock rule. The contact, message, or scene seen after an SQ011 delay is downstream behavior and should not be attributed to the time condition itself.
Delay a follow-up offer after completion
To offer a second quest twelve game hours after the first completes, use two sequential waits before activating any message or reply choice:
Input
-> Pause(completed fact > 0)
-> Pause(game-time delay: 0 days, 12 hours, 0 minutes, 0 seconds)
-> activate offer message
-> activate reply choices
In WolvenKit, give the first Pause Condition a questFactsDBCondition
wrapping questVarComparison_ConditionType: your completion fact,
comparisonType = Greater, value = 0. Give the second a
questTimeCondition wrapping questGameTimeDelay_ConditionType with the
duration above. Connect the first Out to the second In, then the second
Out to the message’s Active socket. Keep reply activation downstream too.
This graph expresses a wait that becomes active after the prerequisite passes. Starting both waits in parallel expresses a different timing requirement: the delay can become active while the prerequisite is still pending. An immediate Condition is also unsuitable if this running route needs to wait for a completion fact that is currently zero.
This follow-up pattern is Experimental until tested in game.
A completion prerequisite and a delay do not replace a separate already-sent guard. Preserve that guard when composing a one-shot offer. Nor can the new gate retract an offer already delivered by an older build: repeat from an untouched pre-install save. Test initial suppression, completion, elapsed game time, reload during the wait, and duplicate prevention separately.
Choose the clock deliberately
Use a realtime delay when the authored meaning is a short wall-clock beat such as debouncing a transition or allowing a presentation step to breathe. Use a game-time delay when the fiction depends on Night City time passing.
Do not choose solely by duration. A ten-second game-time wait and a ten-second realtime wait ask different runtime questions:
| Interruption | Realtime question | Game-time question |
|---|---|---|
| Pause menu | Does wall-clock progress continue? | Does game time stop? |
| Time skip | Is the wait unaffected? | Can the target be crossed immediately? |
| Fast travel | Does loading count? | Does travel advance the target? |
| Save/load | Is elapsed duration restored or restarted? | Is the target timestamp restored? |
| Process restart | Is active condition state serialized? | Is the target reconstructed? |
All answers in that table are Experimental until tested on the pinned build. Enum names and vanilla serialization do not settle them.
Facts are durable design state, not Boolean variables
questFactsDBCondition compares a named fact’s signed integer value. Facts are
often used as zero/nonzero flags, but their native model is an integer:
cqa002_completed Equal 0
cqa002_test_mode Equal 2
cqa002_signal_failed Greater 0
The comparison operators represented in retained resources include Equal,
NotEqual, Greater, GreaterOrEqual, Less, and LessOrEqual.
A fact can make a delayed route re-entry-safe only if the graph checks and writes it at the right boundaries. The fact’s persistence does not magically cancel an active listener or undo a side effect that already ran.
One-shot entry guard
Place a completed guard before the first player-facing or save-backed effect:
Input
-> Condition(completed == 0)
False -> terminating Output
True -> first-run route
Write the completion fact before, or in a tightly controlled sequence with, the final visible completion transition. Then make the False route terminate without reactivating journal entries, starting scenes, or spawning world content.
Use different facts when “started” and “completed” have different meanings. One fact cannot distinguish a partially completed run from a never-started run.
The labs supply a Structurally validated guard. Test re-entry and reinstallation using a completed save to check its one-shot behavior.
Clean-save rule
Facts, journal state, active quest nodes, scene state, communities, and device persistent state can all be save-backed. A save made after an earlier build was installed is not a clean first-run test, even when the archive has since been removed.
For every practical delay or one-shot test, retain:
- one untouched save created before the candidate was first installed;
- one save made while the delay or listener is active;
- one save made after completion;
- the exact installed archive and registration file;
- exact game/mod versions and fresh logs;
- observations and fresh logs for each run.
Never reuse the mid-flow or completed save as the clean replay baseline.
Minimum timer acceptance matrix
| Case | What to record |
|---|---|
| Uninterrupted | Earliest and observed completion time |
| Pause menu | Whether the target advances while paused |
| Save during wait | Remaining time or restart behavior after reload |
| Process restart | Same observation after closing the game completely |
| Time skip | Whether a game-time target is crossed and emitted once |
| Fast travel | Loading/travel effect on both clock and output |
| Completed reload | No second journal activation or delayed output |
| Clean replay | Same first-run result from the untouched save |
Record what happened; do not normalize a surprising result into the behavior you expected. See Lab 2 test protocol for the concrete two-route version of this matrix.
Test pause-menu, process-restart, time-skip, and fast-travel behavior separately when your quest depends on them. For parallel waits, also test simultaneous arrival and a late second input.
Parallel monitors and cancellation
A monitor is a graph route that begins alongside the main route and waits for an event that may or may not occur. Typical uses include optional-objective failure, combat escalation, leaving an area, a device state change, or a timer expiring.
The pattern is powerful because questphase outputs can have several connections. It is also dangerous: the route you stop caring about may still be alive unless the resource explicitly makes a later signal harmless or cuts the target.
Start independent routes
An ordinary Out socket can connect to more than one destination:
Activate optional objective
Out -> wait for failure fact
-> wait for stop condition
-> continue main route
The three connections represent three independent downstream routes. A Hub is not required merely to express the split.
Observed in vanilla: a nested graph in
base\open_world\street_stories\westbrook\japantown\sts_wbr_jpn_03\phase\sts_wbr_jpn_03_streetstory.questphase
starts a main trigger wait and a stealth-objective branch from a shared journal
output. The objective branch starts a Pause Condition for
jpn_03_stealth_fail == 1; later, an immediate Condition reads the same stored
fact. The phase contains no CutSource wiring.
That proves a parallel-listener topology. It does not prove automatic cleanup when the objective succeeds, cleanup on phase exit, save/load restoration, or one-shot emission.
Store the outcome before converging
When two branches can decide one objective, keep their journal writes mutually exclusive and record the result explicitly:
failure listener -> optional Failed -----------------> race input 1
stop listener -> optional Succeeded -> success fact -> race input 2
Do not let both branches write success and failure facts after they converge. The convergence node is a routing boundary, not proof that the losing producer has been destroyed.
Lab 2 follows this rule. Each branch owns one optional-objective outcome, and the main completion route begins only after their XOR-shaped convergence. Its default and source-edited variants exercise different producers without installing two competing test writers in the same graph.
XOR is not a cancellation instruction
Vanilla contains race-shaped XOR convergence, including three independent alert/combat waits in:
base\open_world\street_stories\watson\northside_industrial_district\
sts_wat_nid_03\phases\sts_wat_nid_03_gameplay.questphase
Observed in vanilla: all three waits enter one
questLogicalXorNodeDefinition, then its single output reaches a fact setter.
There are no cut edges from that XOR to the waits.
Therefore the safe statement is “these routes converge through an XOR-typed node.” The following claims remain Experimental:
- first arrival is always the only emitted result;
- losing listeners are cancelled;
- simultaneous arrivals have deterministic priority;
- a later signal cannot retrigger the output;
- race state survives or resets predictably across save/load.
If correctness depends on any of those claims, test it directly and retain the evidence.
Explicit cut topology
questCutControlNodeDefinition owns ordinary In/Out sockets plus a
CutSource. Selected targets expose CutDestination. Vanilla resources wire
the cut source directly to those destinations.
Observed in vanilla: the retained corpus contains 50 Cut Control nodes and
75 CutSource→CutDestination edges; 45 target Pause Condition nodes.
A compact example appears in:
base\open_world\minor_activities\watson\northside\
ma_wat_nid_15\ma_wat_nid_15_phase.questphase
Pause node 225 is a character-killed listener. Cut Control node 236 has a cut
edge to that listener’s CutDestination.
A larger cross-targeted bank appears in:
base\open_world\street_stories\watson\northside_industrial_district\
sts_wat_nid_02\phases\sts_wat_nid_02_ow.questphase
There, four Cut Controls cross-target four active Pause Conditions.
These shapes establish explicit target wiring, not the complete runtime
contract. Raw data and generated class definitions do not prove whether a cut
rolls back state, emits the target’s ordinary output, propagates downstream,
persists in a save, permits later re-entry, or how permanent changes the
operation. Treat those effects as Experimental.
Two safe strategies
Make a late signal harmless
Prefer this when the listener can remain active without cost:
- Store the authoritative outcome in a fact.
- Gate every journal or world-state writer on that outcome.
- Make repeated or losing signals bypass the writer.
- Terminate the containing route cleanly.
- Test the late-signal case explicitly.
This strategy does not assert cancellation. It makes correctness independent of cancellation.
Cut a named target deliberately
Use this only when runtime acceptance has established the required cut behavior:
- Identify the exact listener nodes whose lifecycle must end.
- Connect a Cut Control’s
CutSourceto each target’sCutDestination. - Decide and record the intended
permanentsetting. - Test both outcomes, both arrival orders, and simultaneous completion.
- Save with every target active, then reload and repeat.
- Re-enter the containing phase and verify whether the target can arm again.
Do not copy a large vanilla cut bank as a template. Extract the relevant phase, trace the focused socket edges, and recreate only the topology your own test can defend.
Phase exit is not evidence of cleanup
A terminating output proves that a route reaches the phase interface. It does not, by itself, prove that every independent listener, delay, scene, or world operation started earlier has been cancelled.
Before allowing a phase to terminate, inventory its outstanding work:
| Work that may still be active | Required decision |
|---|---|
| Pause Condition | Remain harmless, satisfy, or explicitly cut |
| Realtime/game-time delay | Ignore late output, gate it, or cut after testing |
| Scene | Map every exit and interruption outcome |
| Community/world spawn | Despawn or transfer ownership deliberately |
| Device persistent-state write | Define restoration and clean-save behavior |
Later chapters add the resource-specific lifecycle rules. At the graph level, the principle is already fixed: every parallel route needs an owner and an end condition.
Monitor acceptance matrix
For each monitor/race pair, retain at least:
| Case | Required observation |
|---|---|
| Main route wins | Optional outcome and final quest state are correct |
| Monitor wins | Failure/escalation outcome and final quest state are correct |
| Near-simultaneous signals | Exactly the behavior the design relies on |
| Late losing signal | No second or contradictory journal/world write |
| Save with both armed | Reload completes or restores without duplication |
| Completed-save reload | No listener reactivation |
| Clean replay | Both variants still work from separate untouched saves |
Keep the installed build and starting save consistent while comparing results.
Retained vanilla condition catalog
This page is a discovery index for condition shapes found in a focused vanilla research set. It answers, “Which concrete type names should I search for?” It does not answer, “Which fields should I copy into a new quest?”
Every positive inventory claim on this page is Observed in vanilla. A type name and an occurrence count establish that a serialized shape exists; they do not establish its truth policy, listener timing, external-reference rules, save/load behavior, or suitability as a reusable recipe.
For the condition families already reduced to focused property descriptions, start with Condition payloads. For predicate composition, see Boolean condition trees.
Corpus and counting method
The retained corpus contains 41 .questphase serializations. All 41 headers
report:
- game resource version
2310; - WolvenKit
8.17.4; - WolvenKit JSON version
0.0.9.
Counts include concrete $type definitions. Wrapper-to-payload counts use
the concrete type beneath type.Data; operation and trigger enums are
counted separately.
A HandleRefId reference does not create another object definition, so it is
not counted again. Conversely, nested predicates are counted even when they
sit beneath one logical tree. These are corpus occurrence counts, not the
number of distinct behaviors, quests, or game-wide uses.
The recursive scan also encounters scnCheck...InterruptCondition and
scnCheck...ReturnCondition objects in scene-related data. Those belong to
scene interruption/return contracts, not the quest gate wrapper layer, and are
reserved for the scene condition index rather than mixed into the tables here.
The surrounding graph contains 262 questConditionNodeDefinition objects and
797 questPauseConditionNodeDefinition objects. Those node counts describe
evaluation schedules. They must not be added to the payload counts or treated
as condition-family popularity rankings. The corpus also contains three
questConditionItem objects used as switch containers; the container name
alone does not define its case-selection policy.
Reader-facing inspection targets Cyberpunk 2077 2.31a and WolvenKit
8.19.0. Re-extract each cited depot resource from your own installation when
the game or serializer changes.
Core state and composition families
| Concrete wrapper | Concrete nested payload or direct field | Retained instances |
|---|---|---|
questFactsDBCondition | questVarComparison_ConditionType | 259 / 259 |
questLogicalCondition | direct operation field | 87 |
questTimeCondition | time payload handle | 168 |
questJournalCondition | journal payload handle | 88 |
The 259 retained fact comparisons use these serialized comparator values:
Greater 138 times, Equal 55, GreaterOrEqual 55, Less 9, and
NotEqual 2. Absence of another enum value from this sample is not evidence
that the engine cannot represent it.
The 87 logical wrappers contain AND 72 times, OR 14 times, and XOR once.
This is a predicate-object inventory, not evidence for short-circuit order or
multi-child XOR truth policy. In particular, the corpus contains no generic
NOT wrapper. Do not invent one from prose shorthand.
The time and journal wrappers resolve to these concrete payloads:
| Wrapper | Concrete payload | Retained instances |
|---|---|---|
questTimeCondition | questRealtimeDelay_ConditionType | 146 |
questTimeCondition | questGameTimeDelay_ConditionType | 19 |
questTimeCondition | questTimePeriod_ConditionType | 2 |
questTimeCondition | questTickDelay_ConditionType | 1 |
questJournalCondition | questJournalEntryState_ConditionType | 66 |
questJournalCondition | questJournalEntry_ConditionType | 21 |
questJournalCondition | questJournalEntryVisited_ConditionType | 1 |
Do not infer clock policy from the four time class names. Do not treat the single visited payload as a universal “the player read something” signal. Each shape still needs its decisive fields, owner, and event source inspected in context.
Spatial families
| Concrete wrapper | Concrete nested payload or direct field | Retained instances |
|---|---|---|
questTriggerCondition | direct type field | 214 |
questDistanceCondition | questDistanceComparison_ConditionType | 55 / 55 |
The retained trigger field values are IsInside 153 times, IsOutside 56,
and Entered 5. These strings are search keys, not proof that one form is
state-recovering and another is edge-triggered across activation, streaming,
teleportation, or save/load.
Every retained distance wrapper uses a
questDistanceComparison_ConditionType. Its 55 comparisons are Greater 33
times, LessOrEqual 11, GreaterOrEqual 10, and Less once. The corpus also
contains 55 questObjectDistance and 55 questValueDistance objects, but
that symmetry does not establish the only legal operand pairing or a universal
measurement unit.
Object, inventory, device, scan, and destruction families
All four payload types below appear beneath questObjectCondition in this
corpus. The wrapper occurs 60 times, exactly matching the nested payload total.
Concrete payload beneath questObjectCondition | Retained instances |
|---|---|
questDevice_ConditionType | 44 |
questInventory_ConditionType | 11 |
questScan_ConditionType | 3 |
questDestruction_ConditionType | 2 |
This shared wrapper is a structural relationship, not evidence that the four payloads share fields or runtime contracts. Before using any one of them, inspect the referenced object identity, the concrete operation/state values, who produces the observed change, and whether the condition represents a state, a transition, or an engine event.
Character, combat, and workspot families
The corpus contains 250 questCharacterCondition wrappers. Their nested
payloads account for all 250 definitions:
Concrete payload beneath questCharacterCondition | Retained instances |
|---|---|
questCharacterSpawned_ConditionType | 73 |
questCharacterMount_ConditionType | 67 |
questCharacterKilled_ConditionType | 37 |
questCharacterStatusEffect_CondtionType | 29 |
questCharacterState_ConditionType | 18 |
questCharacterCombat_ConditionType | 11 |
questCharacterHealth_ConditionType | 4 |
questCharacterHit_ConditionType | 4 |
questCharacterWorkspot_ConditionType | 4 |
questCharacterAttack_ConditionType | 1 |
questCharacterGender_CondtionType | 1 |
questCharacterQuickHacked_ConditionType | 1 |
Condtion in the two class names above is the spelling present in the
serialization; do not silently “correct” native names while searching. The
table does not prove what counts as spawned, killed, mounted, in combat, in a
workspot, or quickhacked. Actor reference form, community ownership,
streaming/readiness, state transitions, and cleanup remain separate evidence
questions.
Scene, content, phone, and system families
| Concrete wrapper | Concrete payload | Retained instances |
|---|---|---|
questSceneCondition | questSceneNode_ConditionType | 31 / 31 |
questContentCondition | questContentLock_ConditionType | 10 of 15 |
questContentCondition | questContentToken_ConditionType | 5 of 15 |
questSystemCondition | questPhoneMuted_ConditionType | 26 of 54 |
questSystemCondition | questCameraFocus_ConditionType | 24 of 54 |
questSystemCondition | questPhone_ConditionType | 4 of 54 |
The editorial grouping reflects the concrete wrapper relationships found in the files. It does not mean that a scene condition owns scene playback, that a content condition grants a token, or that all system payloads share phone lifecycle rules. Locate the corresponding producer and manager nodes before assigning a behavior to any payload.
Vehicle and spawning families
| Concrete wrapper | Concrete payload | Retained instances |
|---|---|---|
questVehicleCondition | questVehicleSpeed_ConditionType | 3 of 6 |
questVehicleCondition | questVehicleTrunk_ConditionType | 2 of 6 |
questVehicleCondition | questVehicleWater_ConditionType | 1 of 6 |
questSpawnerCondition | questSpawnerReady_ConditionType | 7 / 7 |
These rare families are especially poor candidates for copy-by-name recipes. A vehicle predicate still depends on how the vehicle is identified, loaded, spawned, occupied, and cleaned up. A spawner-ready predicate still needs the spawner owner, activation order, failure path, and save/load behavior made explicit.
Representative depot paths
Use these paths to locate focused examples in your own game archives. They are not templates, and their quest-local facts, NodeRefs, actors, devices, scenes, and journal paths are not reusable identifiers.
| Families worth inspecting | Vanilla depot path |
|---|---|
| Facts, logical trees, triggers, time, journal, content | base\open_world\minor_activities\watson\northside\ma_wat_nid_15\ma_wat_nid_15_phase.questphase |
| Game-time and real-time payloads, journal, content token, phone system | base\quest\side_quests\sq011\phases\sq011_concert.questphase |
| Inventory, scan, scene, character, vehicle trunk | base\open_world\street_stories\heywood\vista_del_rey\sts_hey_rey_09\phases\sts_hey_rey_09_openworld.questphase |
| Device, killed, spawned, workspot, quickhack | base\open_world\street_stories\watson\little_china\sts_wat_lch_01\phase\sts_wat_lch_01_combat.questphase |
| Destruction, device, character combat, vehicle speed | base\open_world\street_stories\watson\northside_industrial_district\sts_wat_nid_03\phases\sts_wat_nid_03_openworld.questphase |
| Spawner ready, distance, character mount | base\open_world\street_stories\westbrook\japantown\sts_wbr_jpn_09\phases\sts_wbr_jpn_09_gameplay.questphase |
Extract a named resource with WolvenKit, serialize it for local inspection, and preserve only a focused excerpt or your own notes. Do not redistribute the extracted CR2W or a complete vanilla serialization.
Before treating a condition as reusable
For a family that this book has not yet reduced to a practical guide, record all of the following before presenting an authoring contract:
- The exact immediate or waiting graph node, its socket route, and the concrete condition wrapper stored in its handle.
- The nested payload class and every decisive field, including native enum values and exact spellings rather than inferred labels.
- Ownership and resolution for every external identifier: facts, typed journal paths, NodeRefs, entity or community references, TweakDB records, scenes, devices, vehicles, spawners, and workspots as applicable.
- The producer of the observed state or event and the required activation order. A consumer condition does not create its source system.
- A reduced, mod-owned fixture that survives WolvenKit
8.19.0save/reopen and CR2W round-trip checks with non-null handles and stable concrete types. - In-game cases for already-true activation, normal transition, false or failure state, save/load while waiting, streaming loss and return where relevant, cancellation or phase exit, completion, and re-entry/reset.
- Exact tested Cyberpunk, WolvenKit, RED4ext, ArchiveXL, and dependency versions, plus build hashes, logs, clean starting save, and the result of every acceptance case.
Treat a newly adapted condition as Experimental until the intended activation, completion, interruption, and reload behavior has been tested.
Lab 2: Signal Race
Lab 2 turns a single linear quest into two independently observable routes. It uses an immediate selector, two fact-listening Pause Conditions, two realtime Pause Conditions, a nested Boolean AND, and an XOR-typed convergence without relying on an untested cancellation claim.
| Record | Value |
|---|---|
| Structural validation date | 2026-08-09 |
| Runtime test date | Not yet recorded |
Structurally validated: both checkpoints round-trip with WolvenKit
8.19.0. Follow the test guide for timing, parallel waits, re-entry, and reload.
Follow Author Signal Race in WolvenKit to build the graph from the start checkpoint. Use Test both Signal Race routes to keep the canonical and source-edited candidates, saves, and logs separate.
What the lab demonstrates
The canonical project sets cqa002_test_mode to 2 and takes the stable route:
entry -> completed guard -> test_mode = 2 -> activate quest/objectives
-> immediate selector chooses False
-> wait 120 realtime seconds -> signal_stop = 1
-> waiting AND observes signal_stop > 0 AND test_mode == 2
-> optional objective succeeds -> XOR-shaped convergence
-> required objective succeeds -> completed = 1 -> quest succeeds
A controlled source edit changes only node 11 from exact value 2 to 1:
entry -> completed guard -> test_mode = 1 -> activate quest/objectives
-> immediate selector chooses True
-> wait 30 realtime seconds -> signal_failed = 1
-> failure listener continues
-> optional objective fails -> XOR-shaped convergence
-> required objective succeeds -> completed = 1 -> quest succeeds
Only the selected writer delay starts in either candidate. The other monitor is still a lifecycle obligation, but its reserved fact has no writer in that variant. The lab therefore does not need to claim that XOR cancels a losing timer. Phase-exit cleanup and arbitrary external fact writes remain Experimental.
Prerequisites and downloads
Complete Lab 1 and read:
- Immediate and waiting conditions;
- Boolean condition trees;
- Signal-flow nodes;
- Parallel monitors and cancellation.
Use Cyberpunk 2077 2.31a, WolvenKit 8.19.0, ArchiveXL 1.27.0, RED4ext
1.30.0, and redscript 0.5.31.
- Download the start checkpoint. It has the journal, localization, registration, and a two-node terminating phase.
- Download the completed checkpoint. It has the exact 21-node graph and its review sources.
Do not install the two checkpoints together. They register the same depot paths. Use separate untouched pre-Lab-2 saves for the mode-2 and mode-1 runtime variants; all five tutorial facts and the journal lifecycle are save-backed.
Resource model
| Resource | Depot path | Owns |
|---|---|---|
| Root questphase | mod\cqa\cqa002\phases\cqa002.questphase | Guard, selector, monitors, delays, fact writes, journal operations, convergence, termination |
| Journal | mod\cqa\cqa002\journal\cqa002.journal | Quest, phase, required objective, optional objective |
| Onscreen localization | mod\cqa\cqa002\localization\en-us\onscreens\cqa002.json | Three English strings used by the journal |
| ArchiveXL registration | CQA_Lab02_SignalRace.archive.xl | Root attachment plus journal/localization merges |
The start checkpoint supplies the same journal and localization content as the
completed checkpoint. Its questphase has only Input 0, terminating Output
1, and one ordinary edge. This makes it an executable authoring baseline,
not an empty folder or a hidden generator input.
Journal and localization contract
quests
└── minor_quest
└── cqa002 gameJournalQuest
└── cqa002_01 gameJournalQuestPhase
├── cqa002_01_obj_wait required objective
└── cqa002_01_obj_stable optional objective
| Journal entry | Key | English text |
|---|---|---|
cqa002 title | cqa_cqa002_title | Signal Race |
cqa002_01_obj_wait description | cqa_cqa002_objective_wait | Wait for the signal test to resolve. |
cqa002_01_obj_stable description | cqa_cqa002_objective_stable | Keep the signal stable. |
The stable journal objective has optional: 1. The corresponding questphase
journal-node payloads retain their normal type.optional: 0; do not copy the
journal entry’s optional flag into that unrelated node field. Every typed path
uses fileEntryIndex: 2 because cqa002 is the containing
gameJournalQuest at path component two.
Exact questphase
The completed graph has 21 nodes and 22 edges. Compare its IDs, socket names, and connections with your WolvenKit graph.
| ID | RED node type | Decisive payload | Purpose |
|---|---|---|---|
0 | questInputNodeDefinition | interface In1 | Enter from the registered root |
1 | questOutputNodeDefinition | Terminating, interface Out1 | End completed and bypass routes |
10 | questConditionNodeDefinition | cqa002_completed Equal 0 | Branch immediately around a completed save |
11 | questFactsDBManagerNodeDefinition | set cqa002_test_mode exactly 2 | Select the canonical variant; edit only this value for failure testing |
12 | questJournalNodeDefinition | quest path | Activate and track Signal Race |
13 | questJournalNodeDefinition | required-objective path | Activate the required objective |
14 | questJournalNodeDefinition | optional-objective path | Activate the optional objective and fan out its Out |
15 | questPauseConditionNodeDefinition | cqa002_signal_failed Greater 0 | Wait for the failure result |
16 | questPauseConditionNodeDefinition | AND of stop fact and mode 2 | Wait for the canonical stable result |
17 | questConditionNodeDefinition | cqa002_test_mode Equal 1 | Choose one writer branch immediately |
18 | questPauseConditionNodeDefinition | 30-second realtime delay | Delay the edited failure writer |
19 | questFactsDBManagerNodeDefinition | set cqa002_signal_failed exactly 1 | Fulfil the failure listener |
20 | questPauseConditionNodeDefinition | 120-second realtime delay | Delay the canonical stable writer and leave time for a mid-flow save |
21 | questFactsDBManagerNodeDefinition | set cqa002_signal_stop exactly 1 | Fulfil the stable AND listener |
22 | questJournalNodeDefinition | optional objective, Failed input | Record optional failure |
23 | questJournalNodeDefinition | optional objective, Succeeded input | Record optional success |
24 | questFactsDBManagerNodeDefinition | set cqa002_signal_succeeded exactly 1 | Persist the stable-route outcome |
25 | questLogicalXorNodeDefinition | two inputs, one output | Converge the mutually exclusive outcome routes |
26 | questJournalNodeDefinition | required objective, Succeeded input | Complete the required objective |
27 | questFactsDBManagerNodeDefinition | set cqa002_completed exactly 1 | Persist the one-shot completion guard |
28 | questJournalNodeDefinition | quest path, Succeeded input | Complete Signal Race |
Every supplied node has one job; no node is a magic template. In particular,
ID 16 owns a Boolean condition tree, while ID 25 combines graph signals.
They use similar logical vocabulary at different resource layers.
Exact edge contract
| Source | Destination | Meaning |
|---|---|---|
0.Out | 10.In | Enter guard |
10.False | 1.In | Completed save bypass |
10.True | 11.In | First-run variant setup |
11.Out | 12.Active | Activate quest |
12.Out | 13.Active | Activate required objective |
13.Out | 14.Active | Activate optional objective |
14.Out | 15.In | Arm failure listener |
14.Out | 16.In | Arm stable listener |
14.Out | 17.In | Evaluate selector now |
17.True | 18.In | Edited mode: start failure delay |
18.Out | 19.In | Write failure fact |
17.False | 20.In | Canonical mode: start stable delay |
20.Out | 21.In | Write stop fact |
15.Out | 22.Failed | Fail optional objective |
22.Out | 25.In1 | Failure outcome reaches convergence |
16.Out | 23.Succeeded | Succeed optional objective |
23.Out | 24.In | Record stable success |
24.Out | 25.In2 | Stable outcome reaches convergence |
25.Out1 | 26.Succeeded | Complete required objective |
26.Out | 27.In | Write completion fact |
27.Out | 28.Succeeded | Complete quest |
28.Out | 1.In | Terminate successful route |
The three edges from 14.Out are three connections on one ordinary socket.
There is no Hub. The two edges into Output 1.In also share one destination
socket; socket count is not connection count.
Fact ownership
| Fact | Writer | Reader | Purpose |
|---|---|---|---|
cqa002_test_mode | 11 | 16, 17 | Select and constrain the test variant |
cqa002_signal_failed | 19 | 15 | Failure monitor result |
cqa002_signal_stop | 21 | 16 | Stable monitor stop signal |
cqa002_signal_succeeded | 24 | retained evidence/extension point | Record stable optional success |
cqa002_completed | 27 | 10 | Bypass completed saves |
These names are reserved to Lab 2. Do not use console writes or another mod to drive them during canonical acceptance; doing so changes which producer owns the result.
Common failure modes
| Symptom | Check first | Boundary |
|---|---|---|
| Signal Race never appears | Confirm the .archive and .archive.xl names, all three registered depot paths, and the ArchiveXL/RED4ext logs | Registration or lookup failure; the graph has not yet been exercised |
| Both routes appear, or the wrong route wins | Confirm node 11 is exact mode 2 for canonical or exact mode 1 for the edited candidate, and that only 17.True -> 18 and 17.False -> 20 exist | A dirty save or extra writer invalidates route attribution |
| Optional objective never resolves | Check the matching writer fact, its listener comparison, the delay payload, and the listener’s journal-state socket | Do not infer XOR or cleanup behavior before the route reaches convergence |
| Quest completes twice after reload | Verify the 27 -> 28 order, node 10 completed guard, and a truly untouched versus completed save | A console fact reset is diagnosis, not clean-save evidence |
| Graph looks right but validation fails | Compare node IDs, RED types, socket names, resolved edges, and decisive payloads—not canvas placement or handle IDs | Layout is not serialized behavior |
Lab sequence: All labs · Next: Author Signal Race in WolvenKit.
Author Signal Race in WolvenKit
Expand the Lab 2 start checkpoint into the 21-node Signal Race graph in WolvenKit.
| Record | Value |
|---|---|
| Structural validation date | 2026-08-09 |
| Runtime test date | Not yet recorded |
Use Cyberpunk 2077 2.31a, WolvenKit 8.19.0, ArchiveXL 1.27.0, RED4ext
1.30.0, and redscript 0.5.31. Other versions may expose different editor
wrappers or runtime behavior.
1. Open and isolate the start checkpoint
- Download and extract the start ZIP.
- Open
CQA_Lab02_SignalRace_Start.cpmodprojthrough File > Open Project. - Make an untouched backup of the extracted directory.
- Confirm the project contains three cooked resources beneath
source\archive, their three review JSON files beneathsource\raw, andCQA_Lab02_SignalRace_Start.archive.xlbeneathsource\resources. - Do not install Lab 1, the completed Lab 2 checkpoint, and this start project
as though they were one candidate. Lab 2’s two checkpoints collide on all
three
cqa002depot paths.
The phase should contain only [0] Input connected to [1] Output. Verify
Input socketName: In1, Output socketName: Out1, and Output
type: Terminating before adding nodes.
2. Audit the supplied journal and strings
Open cqa002.journal. Confirm this exact typed tree:
gameJournalRootFolderEntry
└── quests gameJournalPrimaryFolderEntry
└── minor_quest gameJournalFolderEntry
└── cqa002 gameJournalQuest
└── cqa002_01 gameJournalQuestPhase
├── cqa002_01_obj_wait
└── cqa002_01_obj_stable
The required objective has optional: 0; the stable objective has
optional: 1. Both have empty district IDs, zero item/location references,
and the quest has no district or recommended-level dependency.
Open the localization resource at
mod\cqa\cqa002\localization\en-us\onscreens\cqa002.json. Its
localizationPersistenceOnScreenEntries.entries array must join these keys:
secondaryKey | femaleVariant |
|---|---|
cqa_cqa002_title | Signal Race |
cqa_cqa002_objective_wait | Wait for the signal test to resolve. |
cqa_cqa002_objective_stable | Keep the signal stable. |
Each entry uses primary key 0 and an empty male variant. Save, close, and
reopen both resources. Do not edit their raw JSON review copies.
3. Add and number the graph nodes
Open cqa002.questphase, right-click an empty graph area, choose Add Node,
and add the following nodes in the table’s order. The initial Input and Output
already exist.
| Target ID | Add Node label | RED type |
|---|---|---|
10 | Condition | questConditionNodeDefinition |
11 | FactsDBManager | questFactsDBManagerNodeDefinition |
12 | Journal | questJournalNodeDefinition |
13 | Journal | questJournalNodeDefinition |
14 | Journal | questJournalNodeDefinition |
15 | PauseCondition | questPauseConditionNodeDefinition |
16 | PauseCondition | questPauseConditionNodeDefinition |
17 | Condition | questConditionNodeDefinition |
18 | PauseCondition | questPauseConditionNodeDefinition |
19 | FactsDBManager | questFactsDBManagerNodeDefinition |
20 | PauseCondition | questPauseConditionNodeDefinition |
21 | FactsDBManager | questFactsDBManagerNodeDefinition |
22 | Journal | questJournalNodeDefinition |
23 | Journal | questJournalNodeDefinition |
24 | FactsDBManager | questFactsDBManagerNodeDefinition |
25 | LogicalXor | questLogicalXorNodeDefinition |
26 | Journal | questJournalNodeDefinition |
27 | FactsDBManager | questFactsDBManagerNodeDefinition |
28 | Journal | questJournalNodeDefinition |
WolvenKit assigns IDs as nodes are created; a fresh start graph normally uses
2 through 20. Those auto IDs overlap the target range. Do not overwrite a
target ID that another node still owns.
After adding all 19 nodes, use the creation order recorded above to renumber in two passes:
- move the added nodes to unique temporary IDs
100through118, preserving the table order; - move those same nodes from the temporary IDs to target IDs
10through28from the table.
Save, close, and reopen the phase after both passes so the wrappers and
connector owners rebind to the serialized IDs. Confirm the inventory is
exactly 0, 1, and 10 through 28 before following any ID-addressed step.
Leave the root resource’s inplacePhases and phasePrefabs arrays empty. This
lab owns no child phase or world resource. phaseInstancePrefabs belongs to a
questPhaseNodeDefinition; it is not a root questQuestPhaseResource
property.
4. Configure the immediate conditions
For IDs 10 and 17, add a questFactsDBCondition handle at condition,
then add questVarComparison_ConditionType at condition.type.
| ID | factName | comparisonType | value |
|---|---|---|---|
10 | cqa002_completed | Equal | 0 |
17 | cqa002_test_mode | Equal | 1 |
These nodes sample their facts when a signal enters In and choose True or
False. Neither waits for a later value change.
5. Configure the fact writers
Each FactsDBManager node uses questSetVar_NodeType. Set
type.setExactValue: 1 on all five nodes:
| ID | type.factName | type.value |
|---|---|---|
11 | cqa002_test_mode | 2 |
19 | cqa002_signal_failed | 1 |
21 | cqa002_signal_stop | 1 |
24 | cqa002_signal_succeeded | 1 |
27 | cqa002_completed | 1 |
Exact mode value 2 is canonical. Do not change it while building the graph.
Incrementing an old save-backed value would make the test mode depend on prior
runs and invalidate the acceptance design.
6. Configure the waiting conditions
Fact listener at ID 15
Add questFactsDBCondition, then questVarComparison_ConditionType:
| Property | Value |
|---|---|
factName | cqa002_signal_failed |
comparisonType | Greater |
value | 0 |
Boolean AND listener at ID 16
Add questLogicalCondition at condition, set operation: AND, and add two
handles to its conditions array. Each child is a questFactsDBCondition
containing a questVarComparison_ConditionType:
| Child | factName | comparisonType | value |
|---|---|---|---|
0 | cqa002_signal_stop | Greater | 0 |
1 | cqa002_test_mode | Equal | 2 |
This is a Boolean tree inside one Pause Condition. Do not replace it with a graph-level Logical AND node.
Realtime delays at IDs 18 and 20
At each node, add questTimeCondition, then
questRealtimeDelay_ConditionType. Set hours, minutes, and miliseconds to
zero. Set seconds to 30 on ID 18 and 120 on ID 20. These deliberately
long human-test windows make the objective state observable after loading and
leave time to create a mid-flow manual save.
miliseconds is the native RED spelling. Timer behavior through pause,
loading, and save/reload remains part of the runtime test.
7. Configure every journal operation
For each Journal node, add questJournalQuestEntry_NodeType at type, then a
gameJournalPath handle at type.path. Set these common properties:
| Property | Value |
|---|---|
type.optional | 0 |
type.sendNotification | 1 |
type.trackQuest | 1 |
type.version | Initial |
type.path.editorPath | empty string |
type.path.fileEntryIndex | 2 |
Then set paths by target:
| IDs | className | realPath |
|---|---|---|
12, 28 | gameJournalQuest | quests/minor_quest/cqa002 |
13, 26 | gameJournalQuestObjective | quests/minor_quest/cqa002/cqa002_01/cqa002_01_obj_wait |
14, 22, 23 | gameJournalQuestObjective | quests/minor_quest/cqa002/cqa002_01/cqa002_01_obj_stable |
The incoming socket chooses state: IDs 12, 13, and 14 receive Active;
ID 22 receives Failed; IDs 23, 26, and 28 receive Succeeded.
Changing the payload’s path does not compensate for wiring the wrong state
socket.
8. Configure the XOR-typed convergence
On ID 25, set:
| Property | Value |
|---|---|
inputSocketCount | 2 |
outputSocketCount | 1 |
After saving and reopening, confirm ordinary sockets In1, In2, and Out1
plus the inherited CutDestination. The node’s name does not prove automatic
cancellation or exactly-once behavior; those are acceptance questions.
9. Expose and wire all sockets
Right-click each node and choose Show Unused Sockets. Create these 22 ordinary edges:
| Source | Destination |
|---|---|
0.Out | 10.In |
10.False | 1.In |
10.True | 11.In |
11.Out | 12.Active |
12.Out | 13.Active |
13.Out | 14.Active |
14.Out | 15.In |
14.Out | 16.In |
14.Out | 17.In |
17.True | 18.In |
18.Out | 19.In |
17.False | 20.In |
20.Out | 21.In |
15.Out | 22.Failed |
22.Out | 25.In1 |
16.Out | 23.Succeeded |
23.Out | 24.In |
24.Out | 25.In2 |
25.Out1 | 26.Succeeded |
26.Out | 27.In |
27.Out | 28.Succeeded |
28.Out | 1.In |
The three connections leaving 14.Out are intentional. Do not insert a Hub.
The two edges entering 1.In are also valid; they do not require two formal
input sockets.
Leave every CutDestination unwired. Lab 2 deliberately avoids claiming cut
behavior. Do not connect cut sockets to ordinary flow as a guessed cleanup.
Save, close, reopen, and compare against the exact generated graph. Placement and handle IDs may differ; node IDs, RED types, payloads, socket names, and resolved edges must match.
10. Verify registration and project structure
The start checkpoint already contains ArchiveXL registration. Keep its three sections unchanged while authoring:
quest:
phases:
- path: mod\cqa\cqa002\phases\cqa002.questphase
parent: base\quest\cyberpunk2077.quest
journal:
- mod\cqa\cqa002\journal\cqa002.journal
localization:
onscreens:
en-us:
- mod\cqa\cqa002\localization\en-us\onscreens\cqa002.json
base\quest\cyberpunk2077.quest is a vanilla reference. Do not include an
extracted copy in the project.
Keep the start project filename, <Name>, <ModName>, and
CQA_Lab02_SignalRace_Start.archive.xl during the walkthrough. To compare with
the supplied result, open CQA_Lab02_SignalRace.cpmodproj separately. Do not
partially rename the start project.
11. Validate and build
With every tab saved:
- run Project > Scan for broken file references;
- run Project > Scan for broken files;
- run Project > Run File Validation on the entire project;
- resolve all project-owned errors;
- close Cyberpunk 2077;
- choose Build > Install and wait for packing and installation;
- inspect the WolvenKit Log panel and the installed archive plus
.archive.xl.
Use the normal ArchiveXL project install, not Install as REDmod. A packed archive without the loose registration file cannot attach the root or merge the journal/localization resources.
The manually authored start project installs
CQA_Lab02_SignalRace_Start.archive with its _Start.archive.xl. The supplied
canonical completed project installs CQA_Lab02_SignalRace.archive with
CQA_Lab02_SignalRace.archive.xl. Remove one pair before installing the
other; both pairs register the same three depot paths.
12. Create the controlled failure candidate
Keep a copy of the completed build before editing:
- copy the completed project to a separately named working directory;
- open node
11; - change only
type.valuefrom exact2to exact1; - leave
setExactValue: 1and all other files/properties unchanged; - rebuild and label the edited archive as the mode-1 variant;
- install it only after removing the canonical archive;
- load a different untouched save created before any Lab 2 candidate was installed.
Do not flip the fact through a console command for acceptance. Together, the controlled source edit and exact graph establish which producer is wired to the route and create a reproducible candidate artifact. The runtime run records the player-visible outcome; it does not directly observe that internal signal.
Common authoring failures
| Symptom | Check |
|---|---|
| Reopening the phase loses or misbinds connectors | Renumber through temporary IDs 100–118, save, close, reopen, and confirm the exact final ID inventory before wiring |
| A condition branches immediately instead of waiting | IDs 15, 16, 18, and 20 must be Pause Condition nodes; IDs 10 and 17 are the only immediate Condition nodes |
| The stable route never releases | Confirm ID 16 contains one logical AND tree with both fact children and that ID 21 writes cqa002_signal_stop exactly 1 |
| Optional journal state is wrong | Keep the journal entry’s optional: 1 separate from each questphase journal operation’s type.optional: 0; wire Failed and Succeeded sockets exactly |
| Build succeeds but the quest is absent | Install both the packed archive and loose .archive.xl, then inspect ArchiveXL and RED4ext logs for the three depot paths |
| Canonical hashes no longer match | Restore node 11 to exact value 2 and use the supplied completed project; a manually expanded _Start project is only a comparison candidate |
Lab sequence: All labs · Previous: Lab 2: Signal Race · Next: Test both Signal Race routes.
Test both Signal Race routes
Lab 2 has two reproducible source states and therefore needs two separately identified runtime candidates. This protocol prevents a mode-1 failure run from being mistaken for evidence about the canonical mode-2 archive.
| Record | Value |
|---|---|
| Structural validation date | 2026-08-09 |
| Runtime test date | Not yet recorded |
This guide tests both the canonical checkpoint and the deliberately edited timing variant. Keep their installed files, starting saves, and results separate so you can tell whether a failure belongs to the graph or to the changed delay.
Required environment
| Component | Exact version |
|---|---|
| Cyberpunk 2077 for Windows (GOG) | 2.31a |
| WolvenKit | 8.19.0 |
| ArchiveXL | 1.27.0 |
| RED4ext | 1.30.0 |
| redscript | 0.5.31 |
Record the observed values separately in each run. A required version printed in the template is not evidence that the machine actually used it.
Prepare two untouched saves
Before installing any Lab 2 checkpoint:
- choose two manual saves that have never loaded a build registering
cqa002; - copy each whole save-slot directory to retained evidence storage;
- label one for
canonical-cleanand the other forsource-edit-failure; - record the slot-directory name;
- confirm neither save has loaded Lab 2 before; note that history with the save.
A console reset of cqa002_* facts does not create a clean save. Neither does
removing the archive after an earlier load: journal, fact, and active-node state
may already be serialized.
Build and bind the canonical candidate
Use the unmodified completed checkpoint:
candidate id: canonical-mode-2
node: 11
fact: cqa002_test_mode
set exact value: 2
canonical: true
-
Close the game.
-
Build and install the completed project with WolvenKit.
-
Verify that only these candidate files are installed:
archive\pc\mod\CQA_Lab02_SignalRace.archive archive\pc\mod\CQA_Lab02_SignalRace.archive.xl -
Confirm that all three registered depot paths in the
.archive.xlmatch the project.
Run the canonical cases
Load only the save assigned to canonical-clean.
Stable first run
Observe and record:
- Signal Race activates once;
- both objectives activate once;
- the optional objective remains active before the roughly 120-second result;
- it succeeds after roughly 120 seconds of unpaused observation;
- the required objective and quest then succeed once;
- waiting at least 30 additional seconds creates no second journal notification or visible reactivation.
This execution supplies clean-save-stable-route,
immediate-selector-versus-pause, and logical-and-fulfilment. The canonical
source configures the positive case in which both AND comparisons can be
fulfilled; the run records only its corresponding visible outcome. It does not
hold signal_stop > 0 while mode is not 2, so it cannot prove a one-child
negative control. Time observations should be measured and described; do not
infer the selector/monitor distinction only from the final journal screen.
Mid-flow reload
Restore the untouched canonical save, start a separate execution, and save
after both objectives activate but while the optional objective is still
active before its roughly 120-second outcome. Reload that derived save. Record
this as canonical-mid-flow-reload. Label the save and keep fresh logs. Note
whether the remaining delay resumes, restarts, or behaves another way.
The required correctness result is eventual single completion without a duplicate journal activation. The timing result is an observation, not a preselected expectation.
Completed reload and reinstall
Use another execution from the original canonical save. After completion:
- make and retain a completed save;
- use it for
canonical-completed-reload; - reload it without changing the installed files and confirm there is no quest/objective reactivation or delayed visible journal update;
- retain this execution’s fresh logs;
- close the game, remove and reinstall the same candidate;
- use the same completed save for a new
canonical-reinstallexecution; - confirm there is still no reactivation and retain a new log set.
Use the preserved installed files for reinstallation and load the same completed save for both completed-state tests.
Clean replay
Restore the original untouched save directory and repeat the stable route as
canonical-clean-replay. Leave the same build installed and retain fresh logs.
Build and bind the source-edited candidate
Now close the game and remove the canonical archive and registration file. Create the controlled edit described in the authoring chapter:
candidate id: source-edit-mode-1
node: 11
fact: cqa002_test_mode
set exact value: 1
canonical: false
Change no other property or resource. Build and install this mode-1 version separately from mode 2, and use its assigned untouched starting save.
Load only the second untouched save assigned to
source-edit-failure. Give it a distinct save label and retained slot
directory from the canonical original; its bytes may legitimately be identical
if both slots were copied from the same clean baseline. Confirm:
- both objectives activate once;
- the optional objective remains active before the 30-second result;
- the optional objective then fails once;
- the required objective and quest still succeed once;
- waiting at least 30 additional seconds produces no unexpected duplicate completion in this controlled variant.
This supplies source-edit-failure-route and, together with the canonical
run, xor-route-convergence. The exact graph maps the two authored routes to
In1 and In2; the player-visible runs show that both outcomes reach the
common completion sequence. They do not deliver both inputs during one
execution, so they do not prove winner, cancellation, repeat-emission, or
simultaneous-arrival policy.
Retain logs per run
For each of the six executions, close the game after the required observations and retain fresh copies of:
red4ext\plugins\ArchiveXL\ArchiveXL.log
red4ext\logs\red4ext.log
red4ext\logs\game.log
r6\logs\redscript_rCURRENT.log
Hash all four files under that run. Search for the candidate registration file,
cqa002, and errors naming any registered depot path, journal lookup,
localization lookup, or condition resource. Clean logs establish that those
systems did not report an error; they do not replace the in-game route
observations.
Restore canonical source
After the edited run:
- close the game and remove the mode-1 installed files;
- restore node
11to exact value2or discard the edited working copy; - reopen the unmodified completed checkpoint;
- restore either no Lab 2 installation or the unchanged canonical candidate;
- keep both candidates and all six execution/evidence bundles labelled separately.
The downloadable completed checkpoint always remains mode 2. The mode-1
archive is a test artifact, not a second shipped example.
Common test failures
| Symptom | Check |
|---|---|
| Quest is already active or complete on the first run | Stop and replace the save with a whole slot captured before any cqa002 candidate was installed; fact resets do not clean journal or active-node state |
| Optional objective changes too early to observe | Confirm the installed installed files and raw node 18 = 30 seconds / node 20 = 120 seconds; remove stale archives before reinstalling |
| Canonical and edited results cannot be attributed | Keep installed files, untouched-save labels, slot-directory identities, executions, and logs separate; identical clean-save bytes are allowed |
| Reload result is ambiguous | Record whether the timer resumed or restarted, but require only eventual single completion without duplicate activation |
| One evidence note is reused for every execution | Keep separate observations and a fresh four-log set for each run in your test folder |
| Internal node or socket behavior is claimed from the journal UI | Record only visible states and timing; use the exact graph for structural route mapping and leave winner/cancellation/tie policy Experimental |
Lab sequence: All labs · Previous: Author Signal Race in WolvenKit · Next lab: Lab 3: Boundary Check.
Journal, UI, and localization
The journal is the game’s player-facing record of quest state. It owns quest titles, objectives, descriptions, contacts, messages, documents, and map-pin entries. It does not decide when those entries change, create their world targets, resolve every kind of text, or grant rewards by itself.
| Record | Value |
|---|---|
| Practical baseline | Cyberpunk 2077 2.31a, WolvenKit 8.19.0, ArchiveXL 1.27.0, RED4ext 1.30.0, redscript 0.5.31 |
| Mod-owned reference | cqa001 / First Signal |
| Reference status | Lab 1 journal and onscreen resources are Structurally validated |
Keep the owners separate
A journal entry is data. A quest graph requests a state change. A localization resource supplies display text. A streamed world resource supplies the object or marker that a journal mappin can target. A reward node resolves a TweakDB record. Those responsibilities meet through typed paths and identifiers:
ArchiveXL journal registration
|
v
gameJournalResource tree <--- gameJournalPath --- quest journal node
| |
| LocalizationString | state input socket
v v
onscreen localization ----------------> player-facing journal presentation
journal mappin -- NodeRef --> streamed world node
reward node ----- TweakDBID -> reward record
scene line RUID --+--> subtitle map ----> subtitle text
+--> voiceover map ---> WEM audio
scene option ID --> screenplay option --> locStore --> displayed choice text
The arrows are lookup boundaries, not an execution graph. For example, a valid
objective path does not activate itself, and a valid mappin entry cannot make
an unresolved world NodeRef exist.
Read the section in dependency order
| Chapter | Question it answers |
|---|---|
| Trees and paths | How is a .journal resource built, merged, and addressed? |
| Quest state | Which entry types and graph operations produce active, inactive, succeeded, failed, tracked, and visited state? |
| Mappins | Which data belongs to the journal, and where does the streamed-world boundary begin? |
| Messages and onscreens | How do contacts, phone choices, files, emails, and readable entries differ? |
| Localization paths | Why do UI text, spoken lines, and scene choices use three unrelated lookup chains? |
| Rewards and completion | How do payout, the completion fact, journal success, and phase termination remain independent? |
Read Trees and paths and Quest state before changing Lab 1. The other chapters introduce dependencies that First Signal deliberately omits.
One path has several contracts
Consider Lab 1’s objective address:
quests/minor_quest/cqa001/cqa001_01/cqa001_01_obj_wait
That string is useful only when all of the surrounding contracts agree:
| Contract | Lab 1 value |
|---|---|
| Registered journal resource | mod\cqa\cqa001\journal\cqa001.journal |
| Entry chain | primary folder → folder → quest → phase → objective |
| Expected leaf type | gameJournalQuestObjective |
gameJournalPath.className | gameJournalQuestObjective |
gameJournalPath.fileEntryIndex | 2, identifying the containing quest file entry in this path family |
| Requested state | The journal node’s connected Active or Succeeded input socket |
| Visible text key | cqa_cqa001_objective_wait |
| Registered text resource | mod\cqa\cqa001\localization\en-us\onscreens\cqa001.json |
Changing only the path string cannot repair the wrong leaf type, missing merge, wrong state socket, or unresolved localization key. Diagnose those layers separately.
State is save-backed
Journal activation, success, visited flags, tracking, graph checkpoints, and supporting facts can be serialized in a save. They are not one resettable variable. This affects every practical experiment in this section:
- start a first acceptance run from a save made before the mod was installed;
- preserve separate slots for active, visited, and completed states;
- return to the original pre-install save when changing journal IDs or tree shape;
- treat a console fact edit as diagnosis, never as a clean journal reset;
- use a fresh save or fresh world identity when a test also changes a streamed device or mappin target.
The complete procedure is in Install, test, and reset.
How to verify these resources
The chapters cite exact depot paths rather than shipping extracted game files. Use a disposable inspection project and the procedure in Inspect a vanilla questphase to examine only the focused properties needed for a claim. Principal references include:
base\journal\cooked_journal.journalfor journal entry families;base\journal\descriptor.journaldescfor the standard descriptor contract;base\quest\side_quests\sq021\phases\sq021_randys_room.questphasefor a computer-document fact handoff;base\open_world\street_stories\watson\kabuki\sts_wat_kab_05\phases\sts_wat_kab_05_openworld.questphasefor a native reward node.
These are Observed in vanilla reference paths. Do not add them to a tutorial archive, publish their cooked binaries, or paste their complete serializations into a chapter.
Scope boundary
This section explains journal data, graph operations, lookup chains, and their immediate consumers. It does not teach complete device packages, streaming sectors, scene graphs, audio production, or custom TweakDB reward records. Those systems are named where the boundary matters and developed in their own sections. Lab 1 remains intentionally small: it needs only a questphase, one journal tree, one onscreen localization resource, and ArchiveXL registration.
Journal trees and typed paths
A .journal resource defines entries in the game’s merged journal tree. A
questphase does not contain those entries. It addresses them through a typed
gameJournalPath and asks the journal system to change their runtime state.
This chapter explains the resource and lookup contract behind the journal portion of Lab 1. State transitions are covered in Journal state and tracking.
The ownership chain
Four different addresses participate in the Lab 1 lookup:
mod\cqa\cqa001\journal\cqa001.journal depot resource
-> quests/minor_quest/cqa001 journal path
-> cqa_cqa001_title localization key
-> First Signal localized text
The depot path identifies a CR2W file. The journal path identifies an entry inside the merged journal tree. The localization key identifies text in a separately registered onscreen resource. None of these identifiers can replace another.
The questphase owns the operation. The journal owns the target entry. The onscreen localization resource owns the player-facing text. ArchiveXL registers the three resources with their respective roots.
The journal resource root
Create and edit the journal as a CR2W resource in WolvenKit’s property tree. Do not make raw CR2W-JSON editing the authoring workflow.
The Lab 1 resource begins with this native shape:
gameJournalResource
└── entry gameJournalRootFolderEntry
└── entries[] journal entry handles
Set these root properties:
Property below RootChunk | Lab 1 value | Purpose |
|---|---|---|
cookingPlatform | PLATFORM_PC | Cooking target recorded by the resource |
entry | gameJournalRootFolderEntry handle | Root of this contributed tree |
entry.id | default/not serialized in the checkpoint | The root itself is not a named path component |
entry.descriptor.DepotPath | base\journal\descriptor.journaldesc | Soft reference to the vanilla journal descriptor |
entry.descriptor.Flags | Soft | Reference-loading policy used by the validated resource |
entry.journalEntryOverrideDataList | default/not serialized in the checkpoint | No tutorial overrides on the root |
base\journal\descriptor.journaldesc is a vanilla depot reference. Cite or
inspect it; do not add the extracted file to the tutorial project.
Containers and leaf entries
Journal paths come from the id values of nested entries. Container types own
an entries array; leaf types hold the data consumed by a particular UI or
quest system.
The exact Lab 1 chain is:
gameJournalRootFolderEntry id: omitted/default
└── gameJournalPrimaryFolderEntry id: quests
└── gameJournalFolderEntry id: minor_quest
└── gameJournalQuest id: cqa001
└── gameJournalQuestPhase id: cqa001_01
└── gameJournalQuestObjective
id: cqa001_01_obj_wait
The resulting leaf path is:
quests/minor_quest/cqa001/cqa001_01/cqa001_01_obj_wait
There is no path component for the anonymous root entry. There is also no
extra wrapper folder around quests. Adding one changes every path below it.
For the full property table, follow Author the journal tree. That procedure deliberately leaves district, item, marker, and prefab fields empty because Lab 1 owns no world content.
Register the contribution
The file’s presence in source\archive is not enough. ArchiveXL must merge it
into the game journal:
journal:
- mod\cqa\cqa001\journal\cqa001.journal
The value is the cooked depot path beneath source\archive, not a Windows
filesystem path and not the generated .journal.json review artifact beneath
source\raw.
Lab 1 keeps this entry beside its root-questphase and onscreen-localization
registrations in CQA_Lab01_OneShot.archive.xl. See
the complete registration block.
Structurally validated: Lab 1 uses this ArchiveXL 1.27.0 journal
section shape, and repository validation requires the registered depot path to
match the supplied resource. The canonical runtime record remains the
authority for whether the merge was accepted by the pinned game process.
gameJournalPath is a typed lookup
A quest node does not point at a CR2W handle. It carries a
gameJournalPath handle with four fields:
| Property | Meaning |
|---|---|
realPath | Slash-separated IDs from the merged journal tree |
className | CName describing the intended target entry type |
fileEntryIndex | Zero-based component index of the containing gameJournalFileEntry |
editorPath | Editor-facing path text; Lab 1 leaves it empty |
Lab 1’s objective target is:
| Property | Value |
|---|---|
realPath | quests/minor_quest/cqa001/cqa001_01/cqa001_01_obj_wait |
className | gameJournalQuestObjective |
fileEntryIndex | 2 |
editorPath | empty string |
Use the exact target type in tutorial-owned paths. Observed in vanilla:
some vanilla nodes contain legacy or less-specific className values, so the
field should not be used to infer the target when inspecting an unfamiliar
resource. Verify the entry at realPath instead of copying a surprising
vanilla value into a new mod.
How to calculate fileEntryIndex
Several journal entry families derive from gameJournalFileEntry. During
journal-tree traversal, fileEntryIndex records the zero-based depth of the
nearest containing file entry. The registered CR2W can begin above it: Lab 1
starts with an anonymous gameJournalRootFolderEntry, then reaches the
containing gameJournalQuest inside the logical path.
Count from zero:
0: quests
1: minor_quest
2: cqa001 <- gameJournalQuest, the containing file entry
3: cqa001_01
4: cqa001_01_obj_wait
Therefore every Lab 1 path at or below cqa001 uses 2, including the quest
itself and its objective.
Other common families demonstrate why the value is not always 2:
| Example path | Containing file-entry type | fileEntryIndex |
|---|---|---|
contacts/example_contact/... | gameJournalContact at component 1 | 1 |
points_of_interest/minor_quests/... | gameJournalPointOfInterestGroup at component 1 | 1 |
onscreens/emails/quests/minor_quest/example/files/... | gameJournalFileGroup at component 5 | 5 |
onscreens/emails/quests/minor_quest/example/messages/... | gameJournalOnscreenGroup at component 5 | 5 |
gameJournalEmailGroup follows the same containing-file rule as the file and
onscreen groups. The group name and path depth are author choices; calculate
the index from the actual tree rather than memorizing one number.
Inspect a vanilla tree safely
Use the Asset Browser in a disposable project to extract:
base\journal\cooked_journal.journal
This is a large merged vanilla resource. Search for a narrow path instead of reading or publishing the whole serialization. A useful focused observation is:
contacts/dex/q003_flathead_price
Observed in vanilla: that conversation contains a
gameJournalPhoneMessage, a gameJournalPhoneChoiceGroup with one
gameJournalPhoneChoiceEntry, and a following phone message. The containing
gameJournalContact is path component 1, which explains the corresponding
phone-path index.
For a file-backed UI example, inspect the journal path cited by the SQ021 questphase:
onscreens/emails/quests/side_quest/sq021_sick_dreams/
sq021_randy_files/01_cartoon
Its containing gameJournalFileGroup is component 5. The related quest
control is in:
base\quest\side_quests\sq021\phases\sq021_randys_room.questphase
Keep both resources outside the downloadable tutorial. Record their depot paths and your own observations; do not commit the extracted CR2W files.
Save-state boundary
The .journal resource defines entries, while the save can retain their
runtime state. Repacking the definition does not erase an earlier activation,
success, failure, visit, or tracking decision stored in the save.
Use a manual save created before the journal resource was ever installed when testing:
- a new entry or changed path;
- first activation and notification behavior;
- objective and quest completion;
- a phone choice or visited-state condition;
- removal and reinstall of the same build.
Resetting a fact is not equivalent to resetting journal state. Follow the minimum save test matrix.
Failure checks
If a journal lookup fails, check in this order:
- Confirm the
.journalCR2W exists at the registered depot path. - Confirm the ArchiveXL file contains the
journalentry at the correct YAML nesting. - Reopen the journal in WolvenKit and verify the root descriptor and every
entry
idsurvived serialization. - Trace
realPathcomponent by component from the anonymous root. - Confirm
classNamematches the tutorial-owned target type. - Recalculate
fileEntryIndexfrom the containing file entry. - Distinguish a missing entry from a present entry whose saved state prevents the expected transition.
If the entry changes state but its title or description is blank, the journal lookup succeeded. Continue along the separate localization lookup rather than rewriting the journal tree.
Journal state and tracking
A journal entry definition describes what an entry is. A quest graph decides when that entry becomes active, inactive, succeeded, or failed. Visited state and quest tracking are related presentation state, but they are not synonyms for those lifecycle states.
This chapter applies that distinction to the exact Lab 1 nodes. Read
Journal trees and typed paths first if realPath,
className, or fileEntryIndex is unfamiliar.
Definition versus runtime state
The Lab 1 journal defines three quest-lifecycle entries beneath its two container entries:
cqa001 gameJournalQuest
└── cqa001_01 gameJournalQuestPhase
└── cqa001_01_obj_wait gameJournalQuestObjective
Those objects do not carry the current save’s active/succeeded state as an authoring property. The questphase sends operations to their paths at runtime.
Important entry properties are still part of the definition:
| Entry type | Relevant authored properties |
|---|---|
gameJournalQuest | id, title.value, type, districtID, recommendedLevelID |
gameJournalQuestPhase | id, locationPrefabRef |
gameJournalQuestObjective | id, description.value, counter, optional, districtID, itemID, locationPrefabRef |
gameJournalQuestDescription | id, description.value |
Lab 1 leaves district, item, location, and prefab fields empty or zero. That is not a hidden automatic setup: the lab has no world target, item counter, description child, or map pin.
The journal operation node
The graph node is questJournalNodeDefinition. Its type property holds a
node-type handle that supplies the target path and presentation options.
Lab 1 uses questJournalQuestEntry_NodeType with these properties:
Property below type | Lab 1 value |
|---|---|
optional | 0 |
sendNotification | 1 |
trackQuest | 1 |
version | Initial |
path.editorPath | empty string |
path.fileEntryIndex | 2 |
Another common payload is questJournalEntry_NodeType. It contains path and
sendNotification, but not the quest-specific tracking, optional, or version
fields.
Do not infer a target entry’s class from this payload type alone.
Observed in vanilla: some phone-message nodes use
questJournalQuestEntry_NodeType, while other working flows use the simpler
questJournalEntry_NodeType. Inspect the whole node, its sockets, path, and
surrounding presentation intent.
The incoming socket selects the operation
The same node type and journal path can request different states. The incoming socket is decisive:
| Input socket | Requested journal operation |
|---|---|
Active | Make the target active |
Inactive | Make the target inactive |
Succeeded | Mark the target succeeded |
Failed | Mark the target failed |
The node then emits Out. CutDestination, when present, belongs to
interruption routing and is not another journal state.
Not every serialized node retains every unused state socket. Use WolvenKit’s Show Unused Sockets command when constructing the graph, and confirm the specific sockets required by the flow before connecting it.
Lab 1’s exact state sequence
Lab 1 uses four separate nodes so each transition is explicit:
| Node ID | Target | Incoming socket | Result |
|---|---|---|---|
11 | quests/minor_quest/cqa001 | Active | Activate the quest |
12 | quests/minor_quest/cqa001/cqa001_01/cqa001_01_obj_wait | Active | Activate the objective |
14 | same objective path | Succeeded | Succeed the objective after the delay |
16 | quests/minor_quest/cqa001 | Succeeded | Succeed the quest |
The successful flow is:
activate quest
-> activate objective
-> wait ten real-time seconds
-> succeed objective
-> set cqa001_completed = 1
-> succeed quest
-> terminating output
IDs 12 and 14 intentionally share the same payload path. Their different
incoming sockets make them different operations. Editing the objective’s
journal definition cannot substitute for the missing Succeeded edge.
The completion fact is also separate from journal success. Setting
cqa001_completed gives the one-shot guard persistent control state; it does
not automatically mark the quest succeeded. Conversely, succeeding the quest
does not set that authored fact.
See Configure the journal operations and connect every socket for the click-by-click authoring procedure.
State, visited, and tracked are separate
Treat these as separate questions:
| Dimension | Example question | How it is addressed |
|---|---|---|
| Lifecycle state | Is the objective active or succeeded? | Journal-node state socket or state condition |
| Visited state | Did the player open or select the entry? | questJournalEntryVisited_ConditionType |
| Tracking | Is this quest selected for tracker/navigation presentation? | trackQuest on a journal operation plus runtime UI state |
| Notification | Was a presentation request sent for this operation? | sendNotification on the node payload |
Activating a phone choice group does not prove that one choice was selected. Activating a message does not prove that the player opened it. Marking an objective succeeded does not prove that the quest is still tracked.
For a state condition, use:
questJournalCondition
└── type questJournalEntryState_ConditionType
├── path gameJournalPath
├── state Succeeded
└── inverted 0
For a visited condition, use:
questJournalCondition
└── type questJournalEntryVisited_ConditionType
├── path gameJournalPath
└── visited 1
Both condition payloads require the same full typed path discipline described in the previous chapter. A condition can be used beneath an immediate condition node or a wait-until node; that surrounding graph node determines whether the graph samples now or pauses for a later change.
Presentation flags are requests, not proof
sendNotification: 1 requests presentation for the journal operation.
trackQuest: 1 requests tracking behavior where that node/entry combination
supports it. Neither flag proves what the player actually saw.
Presentation depends on more than the two booleans:
- the target entry family and requested state;
- whether its parent entries are available;
- whether localization resolves;
- current tracking and mappin state;
- timing relative to adjacent journal operations;
- state already retained in the save.
Legacy runtime evidence also showed that activating an onscreen entry made it available in the journal without producing an obvious item-acquisition notification. Do not generalize one entry family’s presentation to another.
In Lab 1, preserve the supplied notification and tracking flags. Check the objective text, notifications, tracking, and final Completed presentation in game.
Completion is an ordered lifecycle
A robust success path makes every side effect explicit:
finish the player action
-> succeed the active objective
-> perform any reward or cleanup
-> set the authored completion fact
-> succeed the quest
-> terminate the phase
Lab 1 has no reward or cleanup dependency, so its shorter order is deliberate. Later chapters add those operations without pretending that quest journal success grants a reward or cleans world state automatically.
There is no universal placement for every quest’s reward, cleanup, or completion fact. State the invariant for that flow, then test interruption and reload at the boundaries where partial completion would matter.
Observed in vanilla: the following phase contains multiple objective journal nodes, phone-message presentation, mappin changes, and a separate reward manager in one larger lifecycle:
base\open_world\street_stories\watson\kabuki\sts_wat_kab_05\phases\sts_wat_kab_05_openworld.questphase
Extract it from your own game and inspect focused nodes; do not copy the whole resource or assume its street-story orchestration belongs in a minimal quest.
Save-backed behavior
Journal state is stored in the save. Installing a changed .journal or
.questphase does not rewind an entry that was already activated, visited,
succeeded, failed, or tracked.
Lab 1’s acceptance matrix therefore separates:
- activation from a manual save created before the first install;
- saving and reloading while the delay and objective are active;
- reloading after quest success;
- reinstalling the identical build over the completed save;
- replaying from the untouched pre-install save.
Keep the starting save and fresh logs with your observations. Resetting a fact does not clear the journal; repeat from the pre-mod save for a clean test.
Failure checks
If a transition does not appear:
- Confirm the edge enters the intended state socket, especially
ActiveversusSucceeded. - Confirm
type.path.realPath,className, andfileEntryIndexresolve to the supplied journal entry. - Confirm the journal resource and its ArchiveXL registration loaded before diagnosing graph timing.
- Check whether the entry was already changed in the test save.
- If state changes but text is blank, compare the journal localization key
with the onscreen
secondaryKey. - If text appears but tracking or notification differs, record that outcome separately rather than reporting the entire journal operation as failed.
- Repeat the first-run case from the original pre-install save before changing the resource again.
These checks preserve the difference between a missing resource, a bad typed path, a wrong graph socket, a localization failure, and stale save state.
Mappins: journal intent meets the world
A quest mappin is not a coordinate stored in a questphase. It is a journal
entry that describes presentation and points at a world-owned NodeRef. A
questphase changes the entry’s state; the world resource still has to supply a
resolvable target.
| Record | Value |
|---|---|
| Authoring target | Pinned versions |
| Native type and property shapes | Observed in vanilla |
| New mod-owned mappin lifecycle | Experimental until exercised from a clean save on the pinned baseline |
The examples below are focused resource shapes, not a complete downloadable world project. World-marker and streaming-sector construction is deliberately deferred to World integration.
The ownership chain
questphase
-> questMappinManagerNodeDefinition
-> gameJournalPath
-> gameJournalQuestMapPin
+-> mappinData presentation and routing policy
+-> reference.reference NodeRef
-> world-owned marker or entity
Each link answers a different question:
| Owner | Responsibility |
|---|---|
| Questphase | Decide when the pin becomes active or inactive |
| Journal resource | Own the pin entry, caption key, map style, and target reference |
| World resource | Own the marker or entity identified by the NodeRef |
| Onscreen localization | Resolve the caption key to player-facing text |
| Save | Retain journal and tracking state after activation |
A valid journal path does not create its target. A valid NodeRef does not
make a journal entry active. Packing both resources does not prove that the
target is loaded or that the player sees a route.
gameJournalQuestMapPin
A quest pin normally sits beneath the objective it presents. The exact leaf
type is gameJournalQuestMapPin.
| Property | Type or domain | Responsibility |
|---|---|---|
id | Journal entry ID | Final component of the journal path |
enableGPS | Boolean | Allows the pin to participate in GPS routing; it does not activate the pin by itself |
mappinData.active | Boolean | Authored initial presentation state; runtime journal state is still changed by quest nodes |
mappinData.localizedCaption.value | Localization key | Player-facing caption lookup |
mappinData.mappinType | TweakDBID | Mappin definition, commonly Mappins.QuestStaticMappinDefinition in the cited shape |
mappinData.variant | Mappin variant | Visual role such as DefaultQuestVariant |
mappinData.visibleThroughWalls | Boolean | Whether the world indicator may remain visible through geometry |
reference.reference | NodeRef | World target; this is not a journal ID or graph node ID |
slotName | CName | Attachment slot such as UI_Interaction when the target exposes one |
offset | Vector3 | Local display offset from the referenced target |
uiAnimation | TweakDBID | Optional UI animation record; 0 means none in the cited shape |
mappinData.debugCaption is useful during inspection, but it is not a
replacement for localizedCaption.value. A copied vanilla LocKey#... also
does not become mod-owned text. Use a unique key registered through the mod’s
onscreen localization resource.
The SQ021 journal contains an Observed in vanilla example beneath one of its objectives:
gameJournalQuestObjective
└── gameJournalQuestMapPin id: lab_mp
├── enableGPS: 1
├── mappinData.mappinType: Mappins.QuestStaticMappinDefinition
├── mappinData.variant: DefaultQuestVariant
├── reference.reference: #sq021_mp_inside_lab
└── slotName: UI_Interaction
Inspect it by extracting your own copy of:
base\journal\cooked_journal.journal
Navigate to the SQ021 quest entries rather than redistributing the extracted resource.
Activating and hiding a quest pin
The questphase-side node is questMappinManagerNodeDefinition.
| Property | Required meaning |
|---|---|
path.className | gameJournalQuestMapPin |
path.realPath | Full path to the exact pin entry |
path.fileEntryIndex | Index of the containing quest file entry; 2 for a path shaped like quests/minor_quest/cqa003/... |
path.editorPath | Normally empty in the cited serialized resources |
disablePreviousMappins | Whether this transition also clears earlier mappin/GPS state |
Its normal socket contract is:
| Socket | Direction | Meaning |
|---|---|---|
CutDestination | Cut destination | Interruption obligation; do not guess a connection |
Active | Input | Request the referenced quest pin’s active state |
Inactive | Input | Request its inactive state |
Out | Output | Continue after the state request |
The state is selected by the incoming socket. disablePreviousMappins does not
turn an Inactive connection into an activation, and Out is not evidence
that the UI has already redrawn.
A minimal lifecycle is:
activate and track objective
-> mappin manager through Active
-> wait for the objective's world condition
-> succeed objective
-> same mappin path through Inactive
-> activate the next destination
SQ021 contains an Observed in vanilla manager node at:
base\quest\side_quests\sq021\phases\sq021_randys_room.questphase
The cited node addresses
quests/side_quest/sq021_sick_dreams/randys_room/investigation/investigation_mp
with className: gameJournalQuestMapPin, fileEntryIndex: 2, and
disablePreviousMappins: 0. This is a concrete comparison point, not a whole
phase to copy.
When to clear previous routing
Keep disablePreviousMappins: 0 as the ordinary value unless the transition
is intentionally establishing a replacement destination.
If activating the next destination leaves a stale dotted GPS leg, test
disablePreviousMappins: 1 on that activation. Keep the later deactivation at
0 so it does not clear unrelated routing.
Clearing previous routing can erase another objective’s valid destination. Check these transitions separately:
- the first pin appears;
- its GPS route targets the intended marker;
- inactivation removes its presentation;
- the next pin does not retain a stale route;
- reloading at each boundary produces the same intended state.
Quest pins and point-of-interest entries are different
gameJournalPointOfInterestMappin is a separate journal family. It associates
a map POI with a quest and supports both static and dynamic presentation data;
it is not merely a quest pin with a longer name.
| Concern | gameJournalQuestMapPin | gameJournalPointOfInterestMappin |
|---|---|---|
| Typical owner | Nested beneath a quest objective | Entry beneath points_of_interest/<group> |
| Quest association | Implied by its containing objective path | Explicit questPath handle |
| World target | reference.reference | staticNodeRef and, when used, dynamicEntityRef |
| Presentation data | gamemappinsMappinData | gamemappinsPointOfInterestMappinData |
| Definition fields | mappinType, variant | dynamicMappinDef, staticMappinDef, typedVariant |
| Questphase lifecycle in this chapter | questMappinManagerNodeDefinition Active/Inactive | Do not substitute it for the quest-pin path without evidence for that exact use |
The decisive POI properties are:
| Property | Meaning |
|---|---|
mappinData.active | Authored POI availability in the cited shape |
mappinData.dynamicMappinDef | Dynamic POI definition TweakDBID |
mappinData.staticMappinDef | Static POI definition TweakDBID |
mappinData.typedVariant | Handle containing a typed mappin variant |
mappinData.slotName / slotOffset | Attachment slot and local offset |
staticNodeRef | Static world marker |
questPath | Typed gameJournalPath back to the associated quest |
In base\journal\cooked_journal.journal, the entry at
points_of_interest/minor_quests/mq025_psycho_brawl is Observed in
vanilla with:
staticNodeRef: #mq025_mp_quest_start
questPath.realPath: quests/minor_quest/mq025_psycho_brawl
questPath.className: gameJournalQuest
questPath.fileEntryIndex: 2
dynamicMappinDef: Mappins.DynamicPointOfInterestMappinDefinition
staticMappinDef: Mappins.StaticPointOfInterestMappinDefinition
typedVariant.variant: DefaultQuestVariant
There are two index domains here. If a node addresses the POI entry itself,
the containing file entry in points_of_interest/minor_quests/... is
minor_quests, at index 1. The POI’s internal questPath points into
quests/minor_quest/..., whose containing quest entry is at index 2.
Neither value is a leaf index or handle ID.
The world boundary
Before blaming the journal, prove the target exists in the relevant world scope:
- Resolve the exact
NodeRef, including its#-relative or$-absolute form. - Identify the streaming sector, prefab, or entity that owns it.
- Confirm that resource is installed at the intended depot path.
- Confirm the target is loaded when the pin is activated.
- Check whether
slotNameis valid for the target or whether the pin should use the marker origin plusoffset.
A cross-world entity reference can be valid text and still fail to resolve at runtime. A dedicated static marker at the intended coordinates can make a better quest-pin target than an unrelated interactive device, but constructing and streaming that marker belongs to the world chapter. Do not patch a vanilla sector merely to avoid creating a mod-owned world resource.
Save-aware verification
Mappin activation, journal state, quest tracking, and GPS history can be save-backed. Repacking the same path does not erase an earlier route. A fact edit also does not clear journal or tracker state.
Use a save made before the custom journal and target were first installed for the initial run. Retain separate saves for active-pin, completed-objective, and next-destination reload checks. When changing a world target’s identity or streaming ownership, return to that pre-install save rather than interpreting old state as the new resource’s behavior.
Failure routing
| Symptom | First boundary to inspect |
|---|---|
| Manager node runs but no pin appears | path.className, full realPath, fileEntryIndex, and ArchiveXL journal registration |
| Pin appears at the wrong place | reference.reference, target ownership, slotName, and offset |
| Caption is blank | localizedCaption.value versus the registered onscreen localization key |
| Pin appears but GPS does not route | enableGPS, mappin definition/variant, target resolution, and current tracking state |
| Old dotted route remains | Explicit inactivation order, next activation, then a focused disablePreviousMappins test |
| Pin works only on an old save | Treat that as suspect; repeat from a save that never loaded the custom journal/world identity |
| POI appears but objective pin does not | Verify that the quest pin and POI are separate entries with separate paths and targets |
Validate in layers: resource deserialization, journal path resolution, archive and ArchiveXL registration, world-target resolution, controlled runtime activation, deactivation, reload, and clean replay. No earlier layer proves the later one.
Messages, files, emails, and onscreens
Phone threads and journal-backed computer documents are journal content. A questphase can activate entries and wait for their state, while a device can expose a journal file or email through its persistent controller. A controller can also own inline document content with no journal path. Neither consumer creates the other resource automatically.
| Record | Value |
|---|---|
| Authoring target | Pinned versions |
| Native hierarchies and property shapes | Observed in vanilla |
| New custom phone/device arrangement | Experimental until its own unchanged clean-save acceptance run passes |
This chapter teaches the journal-side contracts. Building a computer’s world node, instance package, persistent controller, and component CRUID bindings is deferred to World integration. A reader does not need a generator, manifest compiler, console mod, or extracted vanilla asset to use the model below.
Three consumers, three responsibilities
questphase
-> journal state nodes
-> contact / conversation / message / choice entries
computer persistent controller
-> filesStructure or mailsStructure
-> journalPath
-> gameJournalFile or gameJournalEmail
optional readable item record
-> secondary action journalEntry
-> gameJournalOnscreen
The shared journal resource owns entry identity and localized fields. Questphases own ordering. Devices own menus, content slots, and read events. Optional item records own inventory presentation. Onscreen localization owns the final UI strings.
Do not infer one chain from another. Activating a gameJournalFile does not
populate an empty device controller, and putting a journal path in a controller
does not make a questphase wait for the resulting fact.
Phone thread hierarchy
The journal tree for a text exchange is:
gameJournalContact
└── gameJournalPhoneConversation
├── gameJournalPhoneMessage
└── gameJournalPhoneChoiceGroup
└── gameJournalPhoneChoiceEntry
The containing contact is the gameJournalFileEntry for phone paths. For a
path such as contacts/cqa_contact/cqa004_offer/..., use
fileEntryIndex: 1: component 1 is cqa_contact. It is not the index of a
message in the conversation.
Contact and conversation properties
| Type | Exact properties used by this model |
|---|---|
gameJournalContact | id, avatarID, name.value, type, isCallableDefault, useFlatMessageLayout, entries |
gameJournalPhoneConversation | id, title.value, entries |
avatarID is a TweakDBID; name.value and title.value are localization
lookups. A text-only contact observed in the retained resources uses
type: Texter and useFlatMessageLayout: 1. Those values describe that shape,
not a rule that every callable or holocall contact must use them.
Message and choice properties
| Type | Property | Meaning |
|---|---|---|
gameJournalPhoneMessage | id | Entry ID used in the full journal path |
sender | Sender role, for example NPC in the cited thread | |
text.value | Onscreen localization key or vanilla localization reference | |
delay | Presentation delay setting for the message | |
attachment | Optional attachment; null in the simple cited messages | |
imageId | Optional image TweakDBID; 0 in the simple cited messages | |
isQuestImportant | Quest-importance presentation flag | |
gameJournalPhoneChoiceGroup | id, entries | Container activated when the player should receive response options |
gameJournalPhoneChoiceEntry | id, text.value | One player response and its label |
isQuestImportant | Quest-importance presentation flag | |
questCondition | Optional condition controlling availability; null in the simple cited choice |
Do not reuse a vanilla LocKey#... as mod-owned text. Create globally unique
onscreen localization keys and register the mod-owned localization resource.
Activate content, then wait for the correct state
A retained simple phone flow uses questJournalNodeDefinition with a
questJournalEntry_NodeType payload.
| Field | Message example | Choice-group example |
|---|---|---|
type.path.className | gameJournalPhoneMessage | gameJournalPhoneChoiceGroup |
type.path.realPath | Full path to one message | Full path to one group |
type.path.fileEntryIndex | 1 for a contact path | 1 for a contact path |
type.sendNotification | Whether this activation should notify | Whether this activation should notify |
That payload shape exposes CutDestination, Active, Inactive, and Out.
Enter through Active to make the entry available. Out continues the graph
after the state request; it does not mean that the player read a message or
selected a reply. Some vanilla phone nodes instead use
questJournalQuestEntry_NodeType and retain additional state sockets; inspect
the actual payload and socket inventory before reproducing a flow.
A two-way exchange therefore has two distinct kinds of wait:
activate opening message(s)
-> activate choice group
-> wait for choice A state Succeeded ----> activate reply A --+
-> wait for choice B state Succeeded ----> activate reply B --+-> final message
-> wait final message visited
For choice selection, place questJournalEntryState_ConditionType beneath a
questJournalCondition in a questPauseConditionNodeDefinition:
| Property | Value |
|---|---|
path.className | gameJournalPhoneChoiceEntry |
path.realPath | Full path to the individual choice, not the group |
path.fileEntryIndex | 1 for the containing contact |
state | Succeeded |
inverted | 0 for the direct condition |
For acknowledgement of the final message, use
questJournalEntryVisited_ConditionType with the final
gameJournalPhoneMessage path and visited: 1. Both pause-condition nodes use
the normal CutDestination, In, and Out sockets.
These state dimensions are not synonyms:
Activemeans the entry was made available;- a choice entry’s
Succeededstate means that choice won; - a message’s
visitedvalue records that the player visited/read it.
Waiting only for activation can pay a reward or finish a quest before the
player sees the response. Conversely, waiting for visited on an entry the UI
never exposes can stall the phase permanently.
A small vanilla phone reference
Observed in vanilla: extract your own copy of
base\journal\cooked_journal.journal and inspect:
contacts/dex/q003_flathead_price
The conversation contains, in order:
gameJournalPhoneMessage q003_01_msg_flathead_price
gameJournalPhoneChoiceGroup q003_02_ch_understood
└── gameJournalPhoneChoiceEntry q003_02a_understood
gameJournalPhoneMessage q003_03_msg_good_luck
It is a compact proof of the hierarchy and fields: the first message has
sender, text, delay, attachment, imageId, and
isQuestImportant; the choice carries text, isQuestImportant, and a null
questCondition; the following message has its own delay. It does not by
itself prove the questphase that activates a new custom thread.
Onscreen, file, and email families
The onscreens primary journal branch contains several leaf families:
| Group | Leaf | Player-facing fields on the leaf |
|---|---|---|
gameJournalOnscreenGroup | gameJournalOnscreen | title.value, description.value, tag, iconID |
gameJournalFileGroup | gameJournalFile | title.value, content.value, pictureTweak, videoResource |
gameJournalEmailGroup | gameJournalEmail | sender.value, addressee.value, title.value, content.value, pictureTweak, videoResource |
tag is a CName; iconID and pictureTweak are TweakDBID values; and
videoResource is a resource reference. Leave optional media values empty
when the design does not use them. Do not copy a vanilla movie, image record,
or localized text into a mod-owned resource.
For a path shaped like:
onscreens/emails/quests/minor_quest/cqa004/files/diagnostic
the containing gameJournalFileGroup is files, component 5, so the
gameJournalPath.fileEntryIndex is 5. The same rule applies to an email or
onscreen group at that position. Count the component contributed as the
containing file entry; do not count handles or the leaf.
The SQ021 computer boundary
SQ021 is the useful vanilla reference because its journal content, device controller, world instance, and quest reaction can be separated precisely.
Extract these resources yourself in a disposable WolvenKit project:
base\journal\cooked_journal.journal
base\quest\side_quests\sq021\phases\sq021_randys_room.questphase
base\worlds\03_night_city\_compiled\default\exterior_19_-8_0_0.streamingsector
base\gameplay\devices\masters\computers\laptop_1.ent
The active journal-backed file is Observed in vanilla at:
onscreens/emails/quests/side_quest/sq021_sick_dreams/
sq021_randy_files/01_cartoon
It is a gameJournalFile. The sibling email group is:
onscreens/emails/quests/side_quest/sq021_sick_dreams/sq021_randy
The laptop controller consumes those entries. The decisive property chains are:
computerSetup.filesStructure[].content[].journalPath
computerSetup.mailsStructure[].content[].journalPath
gamedeviceDataElement.questInfo.factName
Opening the 01_cartoon device element sets
sq021_randy_pc_file_cartoon. A nested branch in
sq021_randys_room.questphase waits for that fact, temporarily sends
ToggleUIInteractivity(false) to ComputerManager, runs the corresponding
scene input, clears the fact, and restores interaction. The quest reacts to
the click; it does not create the Files tab or inject the journal entry.
Likewise, filesMenu: 1 does not produce a Files tab when filesStructure is
empty. The SQ021 controller holds actual file content. Its retained shape has
an inline representation and a journal-backed representation in separate
controller chunks. Treat that as an Observed in vanilla device shape, not
as permission to copy SQ021’s controller package.
Why device construction is deferred
The SQ021 sector binds node-local persistent controller data to
laptop_1.ent through component CRUIDs. A computer can render, animate, and
offer Use while silently falling back to the entity template’s empty
controller when those IDs do not match.
That is a world/device ownership problem. This chapter establishes only the journal-facing requirements:
- the journal entry exists and is registered;
- the controller has non-empty content;
- its
journalPathresolves to the expected leaf type; - the device element owns the intended
questInfo.factName; - the quest waits for that fact separately.
A global .devices or .psrep entry was not required for the cited SQ021
Files UI. A custom registry entry may still be needed when a quest addresses a
device through device-manager operations. Neither observation is a universal
device-construction recipe.
Readable onscreens and the optional item layer
Activating a gameJournalOnscreen makes journal content available. If the
quest also needs item acquisition or pickup presentation, author and test
that item layer separately.
An optional readable item can point its secondary Read action’s
journalEntry at the onscreen path. That gives inventory acquisition and a
route into the same journal content, but it introduces TweakXL and item-record
authoring. TweakXL is not required for phone threads or computer files, and it
is not a hidden prerequisite of this chapter. Defer that layer to a dedicated
readable-item guide rather than presenting a copied record as magic.
Save-backed state
Phone entry state, message visited state, journal content, questphase waits,
and device persistent state can all be stored in a save. Removing the mod or
resetting one completion fact does not restore a clean phone thread or device.
For first-run acceptance, use a save created before the custom journal and
device identity were installed. Keep it untouched. Make separate saves before
a choice, after a chosen reply, and after the final message. When materially
changing a persistent device package, use a new NodeRef or a save that has
never streamed the old identity; an older save can restore copied controller
content that no longer exists in the source.
Verification and failure routing
Test one ownership boundary at a time:
| Symptom | First boundary to inspect |
|---|---|
| Contact or thread never appears | ArchiveXL journal registration, contact ID, full path, className, and fileEntryIndex |
| Opening message appears but choices do not | Choice-group activation through Active, then its exact journal path |
| Both replies appear | Separate Succeeded conditions and branch-to-reply wiring |
| Quest advances before the player reads the final message | Active was treated as visited, or the final visited wait is absent |
| Phone text is blank | Journal text.value versus registered onscreen localization keys |
| Computer has no Files tab | Empty filesStructure, wrong persistent controller, or component CRUID mismatch; filesMenu alone is insufficient |
| File opens but the quest does not advance | gamedeviceDataElement.questInfo.factName, then the quest’s fact condition |
| Journal-backed file is blank | Leaf type/path mismatch, fileEntryIndex, journal registration, or localization lookup |
| Removed emails or files return on one save | Save-backed device persistent state; retry with a fresh identity or pre-stream save |
| Readable entry exists but no pickup appears | Direct journal activation was mistaken for inventory-item presentation |
For phone tests, retain which choice succeeded and whether the final message became visited. For device tests, retain the exact world identity, journal path, read fact, installed resource paths, framework logs, and starting save. Repeat after reload and from a clean replay save before assigning causality to an edit.
Localization paths
Cyberpunk 2077 does not have one universal localization lookup. Journal and onscreen UI, spoken scene lines, and player-choice labels resolve through three different resource paths. A key that is valid in one path does nothing in the other two.
| Record | Value |
|---|---|
| Practical baseline | Cyberpunk 2077 2.31a, WolvenKit 8.19.0, ArchiveXL 1.27.0, RED4ext 1.30.0, redscript 0.5.31 |
| Lab 1 UI resource | Structure checked; verify presentation with the Lab 1 test guide |
| Vanilla scene references | Observed in vanilla |
Choose the lookup by content owner
| Player-facing content | Source identifier | Runtime owner | ArchiveXL section |
|---|---|---|---|
| Quest title, objective, description, journal mappin, phone UI, or onscreen text | Textual LocalizationString.value | localizationPersistenceOnScreenEntries | localization.onscreens.<locale> |
| Spoken scene line | Numeric scnlocLocstringId.ruid | Subtitle map, subtitle entries, VO map, and WEM | localization.subtitles.<locale> and localization.vomaps.<locale> |
| Scene choice label | Numeric scnlocLocstringId.ruid reached through a screenplay option | The scene’s embedded scnlocLocStoreEmbedded | None; it is inside the .scene |
The locale spellings also belong to different schemas. ArchiveXL uses a key
such as en-us; scene locStore descriptors use identifiers such as en_us,
pl_pl, and db_db. Do not normalize one spelling into the other.
Path 1: journal and onscreen UI
A journal field whose RED type is LocalizationString stores a textual lookup
key in value. For example, Lab 1’s objective has this join:
gameJournalQuestObjective.description.value
= cqa_cqa001_objective_wait
|
v
ArchiveXL localization.onscreens.en-us
|
v
localizationPersistenceOnScreenEntries.entries[]
where secondaryKey = cqa_cqa001_objective_wait
|
v
femaleVariant, or a populated maleVariant override
LocalizationString.unk1 appears beside value in a WolvenKit JSON export,
but it is not the textual join. The lookup key is not a scene RUID and should
not be converted into one.
Exact onscreen resource shape
Under the JsonResource root, the decisive types and properties are:
| WolvenKit CR2W tree / JSON path | Type or value | Contract |
|---|---|---|
Data.RootChunk | JsonResource | Container for the localization payload |
Data.RootChunk.root.Data | localizationPersistenceOnScreenEntries | Owns entries[] |
root.Data.entries[] | localizationPersistenceOnScreenEntry | One merged onscreen string |
entries[].primaryKey | 0 for mod-added textual keys | Keep the established mod-entry shape; an export may quote the zero |
entries[].secondaryKey | Text string | Must equal the journal LocalizationString.value and be globally unique after all merges |
entries[].femaleVariant | Text string | Default authored text in the validated shape |
entries[].maleVariant | Text string | Optional gender-specific override; an empty value uses the established fallback convention |
A focused entry therefore has this shape; it is an excerpt, not a complete CR2W resource:
{
"$type": "localizationPersistenceOnScreenEntry",
"femaleVariant": "Wait for the signal.",
"maleVariant": "",
"primaryKey": "0",
"secondaryKey": "cqa_cqa001_objective_wait"
}
Register the cooked depot resource, not the raw CR2W-JSON conversion source:
localization:
onscreens:
en-us:
- mod\cqa\cqa001\localization\en-us\onscreens\cqa001.json
The .json depot file above is a cooked JsonResource. A WolvenKit conversion
source commonly ends in .json.json; that source filename does not belong in
ArchiveXL.
What Lab 1 proves
Lab 1: First Signal supplies a journal resource whose keys all resolve to entries in one registered onscreen resource. WolvenKit 8.19.0 deserialized and round-tripped that structure, so the resource shape and key inventory are Structurally validated.
Lab 1 deliberately uses populated femaleVariant values and empty
maleVariant values. The empty-override shape is structurally consistent with
the established fallback convention. Visible fallback and all Lab 1 text are
governed by the canonical runtime record. If the two variants intentionally
differ, test both player gender configurations rather than inferring one from
the other.
Path 2: spoken scene lines
A spoken line uses one numeric localization ID, but two separately registered resource branches consume it:
scene screenplayStore.lines[].locstringId.ruid
|
+--> ArchiveXL localization.subtitles.en-us
| -> localizationPersistenceSubtitleMap
| -> entries[].subtitleFile
| -> localizationPersistenceSubtitleEntries
| -> entries[].stringId == line RUID
| -> femaleVariant / maleVariant subtitle text
|
+--> ArchiveXL localization.vomaps.en-us
-> locVoiceoverMap
-> locVoLineEntry.stringId == line RUID
-> femaleResPath / maleResPath
-> .wem audio resource
The exact resource fields are:
| Resource type | Decisive properties |
|---|---|
scnscreenplayDialogLine in screenplayStore.lines[] | locstringId.ruid; its separate itemId.id is graph-local and is not the localization ID |
localizationPersistenceSubtitleMap | entries[] |
localizationPersistenceSubtitleMapEntry | subtitleFile resource reference; the inspected quest map also uses subtitleGroup value quest |
localizationPersistenceSubtitleEntries | entries[] |
localizationPersistenceSubtitleEntry | stringId, femaleVariant, and maleVariant |
locVoiceoverMap | entries[] |
locVoLineEntry | stringId, femaleResPath, and maleResPath |
The stringId in both external branches must preserve the same unsigned
numeric value as locstringId.ruid. Do not substitute the screenplay
itemId.id, truncate the value, or regenerate one branch independently.
ArchiveXL registers the subtitle map and the voiceover map separately. The subtitle map then points to the subtitle-entry resource:
localization:
subtitles:
en-us:
- mod\myquest\localization\en-us\subtitles\my_scene_subtitles_map.json
vomaps:
en-us:
- mod\myquest\localization\en-us\vo\my_scene.json
Putting the subtitle-entry file directly in subtitles, omitting the map, or
placing a choice ID in either external branch does not complete the intended
lookup.
For complete spoken dialogue, continue with Author one spoken line. Registering the maps alone does not create the scene, its actors, or audio assets.
Path 3: choice labels and the embedded locStore
Choice labels never pass through the external subtitle or onscreen tables. The scene owns the complete join:
scnChoiceNodeOption.screenplayOptionId.id
-> screenplayStore.options[].itemId.id
-> screenplayStore.options[].locstringId.ruid
-> locStore.vdEntries[] matching localeId + locstringId + signature
-> descriptor.vpeIndex
-> locStore.vpEntries[vpeIndex].content
The corresponding RED types are:
| Location | Type | Decisive properties |
|---|---|---|
| Choice node option | scnChoiceNodeOption | screenplayOptionId.id; caption is only an authoring/debug CName |
| Screenplay option | scnscreenplayChoiceOption | itemId.id, locstringId.ruid |
| Embedded store | scnlocLocStoreEmbedded | vdEntries[], vpEntries[] |
| Descriptor | scnlocLocStoreEmbeddedVariantDescriptorEntry | localeId, locstringId.ruid, signature.val, variantId.ruid, vpeIndex |
| Payload | scnlocLocStoreEmbeddedVariantPayloadEntry | content, variantId.ruid |
caption can make a choice node understandable in WolvenKit, but it is not
the authoritative displayed label and is not a reliable fallback. A retained
bad fixture had the intended caption while the player saw a stale label from
the locStore.
There is no ArchiveXL localization section for this path. The locStore is
embedded in the .scene; adding its numeric ID to onscreens, subtitles, or
vomaps cannot repair the choice lookup.
Preserve descriptor-to-payload identity
vpeIndex is zero-based and must still point at the intended element of
vpEntries[]. The descriptor and its payload also carry the same
variantId.ruid. If entries move, move the descriptor/payload relationship
deliberately and recalculate every affected vpeIndex; a serialize/deserialize
round trip does not repair a mismatched table.
Treat all scene localization and variant RUIDs as unsigned 64-bit values.
Sorting their decimal spellings as text is wrong: for example, 100 sorts
before 20 lexicographically even though its numeric value is larger.
Bound the ordering claim
The known-good authoring shape is deliberately narrower than a universal engine rule:
- Keep
vdEntries[]in contiguous locale blocks. - Within each locale block, sort descriptors by the unsigned numeric value of
locstringId.ruid. - Keep descriptors with the same locale and locstring ID adjacent while
preserving each descriptor’s
vpeIndexandvariantIdrelationship. - Mirror the locale coverage and variant shape of a comparable vanilla scene; do not invent a new locale order from the labels in this chapter.
Four focused references showed locale-grouped descriptors and ascending unsigned locstring IDs within each locale block. These findings are Observed in vanilla:
base\quest\minor_quests\mq003\scenes\mq003_01_homeless.scenebase\quest\minor_quests\mq003\scenes\mq003_03_orbital_pod.scenebase\quest\minor_quests\mq007\scenes\mq007_01_gun_found.scenebase\quest\minor_quests\mq010\scenes\mq010_02_barry_talk.scene
Use locale-grouped descriptors and ascending unsigned locstring IDs within each block. Blank or stale choice labels are a reason to check both ordering and the descriptor-to-payload join.
Vanilla scenes contain both blank-before-source and source-before-blank
db_db pairs. Preserve the exact variant-to-payload mapping from the
comparable scene rather than enforcing one order for every pair.
Use the disposable workflow in Inspect a vanilla questphase
to open these scene depot paths from your own installed archives. Record only
the focused locStore, option IDs, types, and ordering needed for comparison.
Do not add extracted scenes to a distributable project or publish their cooked
files or complete JSON exports.
Full scene construction remains deferred. The lookup contract above does not cover actors, sections, events, lipsync, scene lifecycle, or questphase launch and return paths.
Diagnose the chain that owns the symptom
| Symptom | First checks |
|---|---|
| Journal title or objective is blank | Compare LocalizationString.value with secondaryKey; confirm globally unique key, primaryKey: 0, and localization.onscreens.<locale> registration |
| UI text works for one player variant only | Inspect both femaleVariant and maleVariant; repeat runtime acceptance with both intended configurations |
| Spoken subtitle is missing | Compare line RUID with subtitle stringId; confirm ArchiveXL registers the subtitle map and that subtitleFile resolves |
| Subtitle displays but audio is silent | Compare the same RUID with locVoLineEntry.stringId, then resolve both WEM paths and inspect runtime logs |
| Choice node caption is correct but the menu is blank or stale | Follow option ID to screenplay option, then descriptor to payload; inspect locale grouping, unsigned numeric ordering, vpeIndex, and variantId |
| Only one scene locale is wrong | Inspect that locale block and its payload indices; do not rewrite the external onscreen or subtitle table |
Packing without errors proves only that the resources serialized. It does not prove that the joins resolve at runtime.
Verification and save boundary
Before claiming a path works:
- deserialize each mod-owned CR2W-JSON source with WolvenKit 8.19.0;
- serialize it back and compare the decisive types, IDs, depot paths, array order, and descriptor/payload indices;
- verify every textual or numeric join before packing;
- inspect ArchiveXL and game logs for registration, missing-resource, and lookup errors;
- run journal tests from a pre-install save and scene tests from a save made before that scene became active;
- save and reload during active and completed states, then repeat from the original clean save.
Journal activation and visited state, quest facts, graph checkpoints, and active scene state are save-backed. Resetting a fact does not clear the other records. Use the full matrix in Facts, journals, and saves and Install, test, and reset, and record the exact payload inventory, save history, versions, and observed result.
Rewards and completion
A reward, a completion fact, successful journal presentation, and phase termination are four independent graph effects. A complete quest connects all the effects it needs in an intentional order; none is implied by another.
| Record | Value |
|---|---|
| Practical baseline | Cyberpunk 2077 2.31a, WolvenKit 8.19.0, ArchiveXL 1.27.0, RED4ext 1.30.0, redscript 0.5.31 |
| Lab 1 completion path | Structure checked; verify completion with the Lab 1 test guide |
| Native reward reference | Observed in vanilla |
Observed in vanilla: the reward node below grants an existing reward record. Creating custom TweakDB reward records is a separate task.
Four effects, four owners
| Intended effect | Native graph shape | What it does not do |
|---|---|---|
| Grant payout | questRewardManagerNodeDefinition containing questGiveReward_NodeType | Does not set a completion fact, update the journal, or end execution |
| Persist one-shot completion | questFactsDBManagerNodeDefinition containing questSetVar_NodeType | Does not pay the player or change visible quest state |
| Show the quest as succeeded | questJournalNodeDefinition containing questJournalQuestEntry_NodeType, entered through Succeeded | Does not guarantee a reward or prevent root re-entry |
| End this phase route | questOutputNodeDefinition with type: Terminating | Does not mutate the fact, journal, or inventory |
An objective’s Succeeded state is separate from the containing quest’s
Succeeded state as well. Use socket connections to establish execution order;
screen position in WolvenKit is not evidence of flow. See
Graph execution.
Exact native reward shape
The reward manager’s type handle contains the operation, and the operation’s
rewards[] array contains TweakDBID values:
questRewardManagerNodeDefinition
├── sockets: In, Out, CutDestination
└── type
└── questGiveReward_NodeType
└── rewards[]
└── TweakDBID: QuestRewards.my_quest_completion
A focused CR2W-JSON property excerpt looks like this. Handles and sockets are omitted only to keep the ownership visible:
{
"$type": "questRewardManagerNodeDefinition",
"type": {
"Data": {
"$type": "questGiveReward_NodeType",
"rewards": [
{
"$type": "TweakDBID",
"$storage": "string",
"$value": "QuestRewards.my_quest_completion"
}
]
}
}
}
The node resolves a record that must already exist in TweakDB. A structurally
correct node with a misspelled or absent record can still fail to produce the
intended payout. Defining the QuestRewards.* record, its money, experience,
items, and notification behavior is a separate TweakDB authoring task and is
not taught here.
Reproduce the vanilla observation
Extract this exact depot path from your own installed archives into a disposable WolvenKit inspection project:
base\open_world\street_stories\watson\kabuki\sts_wat_kab_05\phases\sts_wat_kab_05_openworld.questphase
In the focused retained extraction, reward node ID 4 is a
questRewardManagerNodeDefinition; its nested type is
questGiveReward_NodeType, and rewards[] contains the string-backed
TweakDBID QuestRewards.sts_wat_kab_05_completion. That shape is
Observed in vanilla, not a claim that node ID 4 or that reward record is
appropriate for another quest.
The retained reference reported game resource version 2310 and was
serialized for inspection with WolvenKit 8.17.4. Re-extract from the pinned
Cyberpunk 2077 2.31a installation and inspect it with WolvenKit 8.19.0 before
depending on the current shape. Follow Inspect a vanilla questphase,
retain only focused notes and hashes, and do not ship the cooked phase or a
complete JSON serialization.
A known-good completion order
The retained full quest fixture uses this sequence:
wait until the final phone message is visited
-> grant QuestRewards.<quest>_completion
-> set <quest>_completed exactly to 1
-> enter the quest journal node through Succeeded
-> enter a Terminating output
Its concrete operation families are:
| Step | Node and decisive property |
|---|---|
| 1 | questPauseConditionNodeDefinition → questJournalCondition → questJournalEntryVisited_ConditionType, with the final message’s gameJournalPath and visited: 1 |
| 2 | questRewardManagerNodeDefinition → questGiveReward_NodeType.rewards[] |
| 3 | questFactsDBManagerNodeDefinition → questSetVar_NodeType, with the completion fact, setExactValue: 1, and value: 1 |
| 4 | questJournalNodeDefinition → questJournalQuestEntry_NodeType, connected to the Succeeded input socket for the quest path |
| 5 | questOutputNodeDefinition, type: Terminating |
Replace the final-message gate with the last player-facing event your quest requires. Wait for an asynchronous scene, message, or interaction to finish before declaring the route complete.
Why this order is not atomic
The chain is a sequence of graph operations, not one transaction:
- granting the reward before writing the completion fact avoids a state where the one-shot guard suppresses a payout that has not happened yet;
- a save or interruption after the reward but before the fact can instead expose a duplicate-payout window on re-entry;
- writing the fact before quest journal success prevents a later root evaluation from replaying the normal route, but an interruption can leave the visible journal in an intermediate state;
- termination ends the current path only after the persistent and visible effects have been requested.
There is no universally safe order without considering re-entry, checkpoints, and whether the reward operation is idempotent. Treat the sequence above as a known-good starting point and run interruption tests at every boundary that can be saved. If the quest can enter completion twice through parallel routes, join or guard those routes before the reward node rather than hoping the final fact write will deduplicate them.
Reconcile Lab 1
Lab 1: First Signal intentionally has no reward record and no reward manager. Its ending is:
succeed the objective
-> set cqa001_completed exactly to 1
-> succeed quests/minor_quest/cqa001
-> terminate
Lab 1 has no payout. Its fact write, quest success, and termination are separate operations. Follow the test guide for first load and reload checks.
To add a payout in a later lab, first create and validate a quest-owned
QuestRewards.* record through the future TweakDB procedure. Then insert the
reward manager before the completion fact and repeat the entire save matrix.
Do not point Lab 1 at the named vanilla record or copy the vanilla phase.
Failure checks by owner
| Symptom | Inspect first |
|---|---|
| Reward node runs but no payout appears | Exact TweakDBID, existence and contents of the reward record, node In/Out connections, and runtime logs |
| Payout appears but the quest stays active | Quest journal path, className, fileEntryIndex, and connection to the quest node’s Succeeded socket |
| Quest succeeds but restarts on a later load | Completion fact name/value, root one-shot condition, and whether the fact write is reachable before termination |
| Quest stops changing before the payout | Final wait condition, especially visited versus merely active journal state |
| Payout occurs twice | Parallel completion routes, reward-to-fact interruption window, checkpoint resume point, and save history |
| Journal succeeds but the graph remains active | Connection from the journal node’s Out socket to a Terminating output |
| Objective succeeds but quest does not | Confirm there are distinct objective and quest journal nodes and that both receive the intended state |
Do not diagnose a missing TweakDB record by changing journal state, and do not diagnose a bad journal path by duplicating the reward node. Each test should isolate one owner.
Verification and save boundary
For a payout-bearing completion route:
- structurally inspect the reward node, its nested operation, every reward
TweakDBID, and the connected socket pair; - verify the completion fact has one meaning and the root guard reads that same fact;
- verify objective success, quest success, and termination are distinct connected operations;
- start from a pre-install save and record inventory, money, experience, fact, journal state, and logs before and after completion;
- test a save before the final presentation gate, after the reward if the game permits it, after the fact write, and after termination;
- reload the completed save and prove the quest neither reactivates nor pays again;
- return to the original clean save for the next first-run test.
Facts, journal states, visited flags, graph checkpoints, scenes, communities, and device persistent state can all survive in a save. A console fact reset is not a clean replay and cannot establish one-shot correctness. Use Facts, journals, and saves and Install, test, and reset, and bind the archive filenamees, exact versions, save history, and retained logs to the test record before changing an Experimental claim to Runtime-proven.
World integration
A quest can name a place, but the streamed world must supply the thing at that place. The useful mental model is therefore a chain of owners:
ArchiveXL registration
-> streaming block
-> sector descriptor
-> streaming sector
-> node data + registered NodeRef + concrete node
-> questphase or journal reference
No link substitutes for another. A valid quest graph does not create its trigger, and a visible world marker does not prove that a questphase declared the prefab root needed to address it.
What owns what
| Owner | Responsibility in this section |
|---|---|
worldStreamingWorld | References the world’s compiled streaming blocks and global world resources. |
| ArchiveXL configuration | Adds a mod-owned block to the game’s streaming registration without replacing the base streaming world. |
worldStreamingBlock | Holds sector descriptors, including sector depot paths, streaming bounds, categories, levels, and quest-prefab roots. |
worldStreamingSector | Holds or references concrete world nodes, their placement records, and registered NodeRefs. |
questQuestPhaseResource | Executes quest logic and carries quest-prefab dependency declarations. |
| Journal resource | Owns journal presentation and map-pin records; it can refer to a world marker but does not place one. |
| Save | Retains facts, journal state, checkpoints, and some world/device state independently of the installed archive. |
This ownership split builds on the resource model, identifier domains, and persistent state.
Reading route
Read the chapters in order:
- Streaming model — world, block, descriptor, sector, and registration responsibilities.
- Quest prefabs and NodeRefs — the single-phase dependency and lookup chain from a questphase to a sector node, plus the unresolved nested-phase boundary.
- Sector nodes and placement — concrete
nodes,
nodeData,nodeRefs, transforms, and inplace resources. - Triggers and areas — outlines, notifiers, state-shaped and edge-shaped conditions.
- Markers and navigation — world markers, journal pins, navigation endpoints, and route expectations.
- Devices and persistence — entity nodes, slots, controller state, and fresh NodeRef identity.
- Advanced devices, interactions, and persistent state — world identity, template/component binding, command-versus-observation semantics, cancellation, and a clean-save device matrix.
- Location research — extracting quest-safe vanilla references without redistributing them.
- Lab 3: Boundary Check — the learner-facing resource and graph contract.
- Author Boundary Check in WolvenKit — every supplied resource and property explained.
- Test Boundary Check — clean-save, reload, streaming, and boundary acceptance cases.
Lab 3 design boundary
Structurally validated: Boundary Check uses one quest prefab root, one
Quest sector containing a reach trigger and a leave trigger, and one
AlwaysLoaded sector containing the checkpoint marker. Its candidate site is
near world position (-1000.02, 1497.2208, 8.3). The geometry is a 25-unit
reach area with 16 outline points and height 12, plus a 110-unit leave area with
20 outline points and height 16. The graph uses state-shaped IsInside and
IsOutside conditions. The acceptance matrix tests ordinary entry, reload
before the reach crossing, and reload after reach while the player is still
inside the outer leave area. It does not claim coverage for loading while an
active reach condition is already inside its area.
These numbers came from prior research. Their serialization is checked; finite padded block bounds, marker visibility, trigger firing, vertical coverage, navigation behavior, and reload semantics are runtime acceptance claims governed by the recorded matrix. The lab intentionally omits devices, child phases, communities, and scenes so each failure has a small search surface.
First-pass failure routing
| Symptom | Inspect first |
|---|---|
| Nothing from the mod-owned world content appears | ArchiveXL block registration, exact depot path, and ArchiveXL logs |
| One sector is absent | Its block descriptor data, category, level, and streaming box |
| The object exists but quest logic cannot resolve it | phasePrefabs, descriptor questPrefabNodeRef, full sector NodeRef, and local questphase NodeRef |
| The wrong object or transform is used | nodeData.NodeIndex, the corresponding nodes[] entry, position, orientation, scale, and bounds |
| A journal pin has UI state but no expected world anchor | Journal mappin data and the marker node/NodeRef are separate owners; inspect both |
| A device is visible but its prompt or authored content is absent | Entity-template interaction/controller components, CRUID binding, node-local RedPackage, workspots, slots, and placement clearance |
| A device works on a fresh save but exposes old state on another save | Persistent NodeRef identity, controller state, active graph/journal history, and whether that save streamed an earlier package |
| A result changes between new game, reload, and an old save | Treat save-backed facts and journal state as a separate variable before editing more CR2W fields |
Packing, registration, and a clean WolvenKit round trip are intermediate checks. Runtime acceptance must begin from a save made before any version of the test quest was installed, then cover normal entry, reload before reaching, reload between the two boundaries, departure through the outer area, stream-away/return while reach is active, completion, and completed-save reload. This matrix does not make a separate post-completion re-entry claim. Changing an archive cannot reset a fact or journal state already stored in a save. The full evidence ladder is in Lifecycle, cleanup, and evidence.
Streaming model
World integration starts with a registration path, not with a loose sector file. A mod-owned node becomes discoverable only when a registered streaming block has a descriptor that points to its sector and gives the engine enough context to stream it.
Vanilla references
These focused base-game resources are useful comparisons. Extract them from your own installation; they are references, not files to copy into the project.
base\worlds\03_night_city\_compiled\default\03_night_city.streamingworld
base\worlds\03_night_city\_compiled\default\blocks\all.streamingblock
base\worlds\03_night_city\_compiled\default\quest_606b61008df2ba6f.streamingsector
base\worlds\03_night_city\_compiled\default\always_loaded_0.streamingsector
The ownership chain
worldStreamingWorld
└─ blockRefs[]
└─ worldStreamingBlock
└─ descriptors[]
└─ worldStreamingSectorDescriptor
├─ data -> *.streamingsector
├─ streamingBox
├─ category
├─ level
└─ questPrefabNodeRef
The base worldStreamingWorld is useful research evidence, but a mod should
not replace it merely to add a sector. ArchiveXL can register a separate,
mod-owned block:
streaming:
blocks:
- mod\cqa\cqa003\world\cqa003_boundary.streamingblock
That is Lab 3’s structurally checked registration path. The runtime protocol
must still bind the installed .archive.xl hash and confirm that ArchiveXL
logged its registration.
worldStreamingWorld
The world root’s blockRefs identifies compiled streaming blocks. The same
resource also has references for global world systems, including device,
mappin, point-of-interest mappin, area, loot, and location resources. Those
references show that world registration is broader than quest placement;
they do not mean a quest author should edit every global resource.
For a focused quest addition, inspect the world root to understand the native shape, then add the mod block through ArchiveXL. Replacing the vanilla root would couple a small quest to a large, shared resource and create needless conflict and redistribution risk.
Blocks and descriptors
worldStreamingBlock contains a block index and a descriptors array. Each
worldStreamingSectorDescriptor supplies the streaming metadata for one
sector.
| Descriptor property | Role | Evidence boundary |
|---|---|---|
data | Async resource reference to the sector depot path | Observed in vanilla |
streamingBox | World-space box used by the streaming system | Shape Observed in vanilla; exact load/unload behavior must be runtime-tested |
questPrefabNodeRef | Full quest-prefab root associated with a Quest descriptor | Observed in vanilla and part of the structurally validated prior fixture |
numNodeRanges and variants | Cooked streaming metadata | Observed in vanilla; do not invent semantics from field names |
blockIndex | Descriptor’s block association | Observed in vanilla |
level and category | Streaming classification values | Values are observable; universal policy is not established |
The worldStreamingSectorCategory values present in the retained type
information are Exterior = 0, Interior = 1, Quest = 2, Navigation = 3,
AlwaysLoaded = 4, and Unknown = -1. This enum explains serialized values;
it does not prove when every category loads or unloads.
In the structurally validated prior fixture, the Quest sector root used
category Quest and level 255, while its block descriptor used category
Quest and level 0. The AlwaysLoaded sector and descriptor used category
AlwaysLoaded and level 1. Preserve known-good values when studying that
shape, but do not elevate this small sample into a universal invariant.
The vanilla mq003 comparison likewise has a Quest sector root at level 255
and its Quest block descriptor at level 0; this is Observed in vanilla.
The retained vanilla always_loaded_0.streamingsector root is category
AlwaysLoaded at level 255. These examples reinforce that sector-root and
descriptor values occupy different records, while also showing that level is
not a single constant for every AlwaysLoaded arrangement.
Quest and AlwaysLoaded responsibilities
Lab 3 separates responsibilities:
| Sector | Content | Why it is separated |
|---|---|---|
| Quest | Reach and leave trigger nodes | The descriptor can bind the quest-prefab root used by quest logic. |
| AlwaysLoaded | Checkpoint marker node | Its registration is kept in a separate sector category from the bounded trigger sector. |
Experimental runtime boundary: the checked structure is not a promise that any node in an AlwaysLoaded sector is perpetually active under every runtime condition. Nor is AlwaysLoaded a reason to put all quest content into one sector. Category, block bounds, node bounds, distance fields, and game state can have distinct roles that must be isolated in game.
The focused vanilla Quest sector at
quest_606b61008df2ba6f.streamingsector is useful because its retained
extract contains AI spots, trigger areas, splines, and a vehicle-forbidden
area under a quest-prefab namespace.
A separate derived world-asset index catalogs 23 worldStaticMarkerNode
records across always_loaded_0, always_loaded_1, and
always_loaded_2.streamingsector. One representative record is node index
1550 in always_loaded_0.streamingsector, with debug name
q104_02a_sm_breaking_antena_carmocap. That index was built from 760
serialized sectors and 10,055 selected assets; it is not an exhaustive node
census or a substitute for inspecting the cited depot resource. This is
Observed in vanilla support for the category pattern, not proof that Lab
3’s marker will load or display correctly.
Bounds are part of the experiment
A descriptor streamingBox and a sector node’s placement bounds are separate
records. A box should enclose the content it is intended to stream, with
deliberate padding for approach and test movement. Making a box finite is more
diagnostic than using an unexplained enormous volume: the author can test
outside, approach, inside, reload, and departure states.
Do not infer exact activation distance from streamingBox alone. Node data
also contains values such as MaxStreamingDistance, while retained WolvenKit
schemas expose fields named UkFloat1 and Uk10 through Uk14. Those Uk*
names are opaque serialization labels. Preserve known-good values and change
one variable at a time; do not rename them conceptually as proven range or
culling controls.
Validation sequence
- Open the block and every referenced sector in the pinned WolvenKit version.
- Confirm exact depot paths, descriptor categories, levels, bounds, and quest-prefab roots.
- Save, convert or pack as required, then round-trip the CR2W and re-inspect the same properties.
- Confirm the intended files are present in the packed archive.
- Confirm ArchiveXL registered the mod-owned block in its log.
- Test approach, entry, active-state save/reload, ordinary stream-away and return, departure, and completed-save reload from a clean starting save.
Steps 1–4 can support Structurally validated. Step 5 proves registration, not player-facing behavior. Only the controlled in-game cases can support a bounded Runtime-proven claim. See Lifecycle, cleanup, and evidence.
Common failures
| Symptom | Likely boundary |
|---|---|
| No mod-owned sector appears | ArchiveXL config not installed, block path misspelled, or registration failed |
| One sector fails while another works | Its descriptor data path, category, level, or serialized sector is wrong |
| The sector appears only at surprising distances | Descriptor box, node bounds, and streaming-related node fields need isolated testing |
| A trigger exists but a quest reference does not resolve | Streaming succeeded; inspect the separate quest-prefab and NodeRef chain |
| WolvenKit opens the file but the game ignores it | Editor acceptance did not prove packing, registration, external references, or runtime compatibility |
| A resource edit appears ineffective on an old save | Quest facts, journal state, or a previously reached checkpoint may already select another lifecycle route |
An archive change cannot erase save-backed facts or journal state. Use a save created before any version of the lab was installed for the acceptance matrix, and also test a mid-flow reload as its own case. Resetting one quest fact is not a substitute for diagnosing an unregistered block or bad sector path.
Quest prefabs and NodeRefs
In the compact single-phase arrangement used for this section, a quest NodeRef has four links to audit: the phase’s prefab dependency, the block’s root binding, the sector’s full child registration, and the sector’s authored node arrangement. Nested phases add a dependency-lifetime question that the retained evidence does not settle universally.
questphase local NodeRef
-> questphase.phasePrefabs root declaration
-> block descriptor full questPrefabNodeRef
-> sector full NodeRef registration
-> sector nodeData / concrete-node arrangement
This chain explains why copying a plausible #child string into a quest graph
is insufficient.
Vanilla references
These focused base-game resources are useful comparisons. Extract them from your own installation; they are references, not files to copy into the project.
base\quest\main_quests\part1\q108\phases\q108_06b_tower_mainframe.questphase
base\open_world\street_stories\watson\little_china\sts_wat_lch_01\phase\sts_wat_lch_01_phase.questphase
base\open_world\street_stories\watson\little_china\sts_wat_lch_01\phase\sts_wat_lch_01_openworld.questphase
base\worlds\03_night_city\_compiled\default\blocks\all.streamingblock
base\worlds\03_night_city\_compiled\default\quest_606b61008df2ba6f.streamingsector
base\worlds\03_night_city\_compiled\default\always_loaded_0.streamingsector
A quest prefab is a namespace and binding
In this workflow, “quest prefab” names the root beneath which related world
NodeRefs are resolved. It does not imply that a separate .prefab CR2W file
must exist. The relevant serialized owners are:
| Link | Owner and property | Example form |
|---|---|---|
| Direct dependency | questQuestPhaseResource.phasePrefabs[] → questQuestPrefabEntry.prefabNodeRef | #cqa003_pr_boundary |
| World binding | worldStreamingSectorDescriptor.questPrefabNodeRef | $/mod/cqa/cqa003/#cqa003_pr_boundary |
| Placed child identity | sector nodeData[].QuestPrefabRefHash | $/mod/cqa/cqa003/#cqa003_pr_boundary/#cqa003_tr_reach |
| Registered child identity | sector nodeRefs[] | The same full child path |
| Concrete object | sector nodes[], associated with nodeData[].NodeIndex in the compact authored shape used by this lab | A trigger or marker node |
All cqa003 values in this table are the exact Structurally validated
serialized values. Their runtime resolution remains Experimental.
Local and full paths are different representations
A local questphase reference is normally written relative to a declared root:
#cqa003_tr_reach
The world-side registration includes the depot namespace, root, and child:
$/mod/cqa/cqa003/#cqa003_pr_boundary/#cqa003_tr_reach
The root itself appears in local form in phasePrefabs and full form on the
block descriptor:
phasePrefabs prefabNodeRef: #cqa003_pr_boundary
descriptor questPrefabNodeRef: $/mod/cqa/cqa003/#cqa003_pr_boundary
Do not compare these strings as though one were a typo merely because one has a prefix. Compare each value in its identifier domain. The identifier domains chapter distinguishes NodeRefs, TweakDBIDs, CNames, facts, and depot paths.
What each link proves
phasePrefabs declares a dependency; it does not create or place anything.
The descriptor root associates a Quest sector with a prefab namespace; it
does not prove the child exists. A full path in nodeRefs registers an
identity; it does not by itself describe the concrete node. In a compact
mod-owned sector, nodeData.NodeIndex associates the placement with its local
nodes[] object. Cooked vanilla sectors can contain non-local-looking indices
or placement data without concrete nodes in the same retained view, so that
local lookup is not a universal file invariant.
For Lab 3’s compact single-root phase, review one child through every owner:
- Find the local NodeRef in the quest node or condition payload.
- Find its root in that questphase’s
phasePrefabs. - Find the same root in full form on the intended block descriptor.
- Find the full child path in the sector’s
nodeRefsand node data. - In the authored mod sector, confirm
NodeIndexassociates that placement with the intended concrete node; do not infer the relation from row order. - Confirm that the block and sector depot paths are actually registered and packed.
The retained vanilla block supplies a concrete comparison: a Quest descriptor
points to
base\worlds\03_night_city\_compiled\default\quest_606b61008df2ba6f.streamingsector
and carries a full quest-prefab root ending in #mq003_pr_homeless. The
focused sector extract then registers full child NodeRefs beneath that root.
This is Observed in vanilla; it is evidence for the relationship, not a
template to copy blindly.
Dependency locations across phases
Two serialized locations are visible in the retained evidence:
- root
questQuestPhaseResource.phasePrefabs; questPhaseNodeDefinition.phaseInstancePrefabson a phase node.
They are distinct properties. Their exact activation and lifetime semantics
cannot be derived from names alone. In the retained 41-resource vanilla
survey, 16 resource roots had non-empty phasePrefabs, containing 20 entries
in total. Ten phase-node records across three files had non-empty
phaseInstancePrefabs. Every surveyed root inplacePhases was empty.
Observed in vanilla: sts_wat_lch_01_phase.questphase declares
#sts_wat_lch_01_streetstory at its root. Its external open-world child has
empty root phasePrefabs yet directly uses trigger NodeRefs; the inspected
phase nodes also have empty phaseInstancePrefabs. This disproves a universal
claim that every external child always repeats every root it directly uses.
It does not, by itself, prove the engine’s complete inheritance or lifetime
rule.
For mod-owned resources, choose the declaration owner from a complete, evidence-matched lifetime rather than duplicating roots by default. Lab 4 keeps the declaration on its registered root while the external child and invoking node stay empty; that arrangement is backed by a retained runtime fixture and a matching vanilla shape, while Lab 4’s exact new runtime remains Experimental. Repeating a root in a child is a different arrangement that needs its own validation, not a harmless conservative default. Lab 3 intentionally stays in one root phase, so its first world test does not depend on either external-child arrangement. Prefab dependencies contains the book’s review procedure; apply its ownership rule within this evidence boundary.
Names are part of persisted identity
A NodeRef is an asset identity, while facts and journal state are separately saved lifecycle data. Not every NodeRef is itself proof of persisted runtime state, but changing a NodeRef can orphan references and, for stateful devices, can select a different persistent identity. Later device work must record when a materially changed device needs a fresh NodeRef.
For the trigger-and-marker lab, test with a save made before any version of
the quest was installed. Reload once before crossing the reach boundary, and
reload a separate save after reach has succeeded while the player remains
inside the outer leave area. Those cases exercise restoration before entry and
the active IsOutside wait after reach. They do not establish how an active
IsInside wait behaves when loaded with the player already inside its area.
All runtime behavior remains Experimental until the recorded matrix passes.
Common failures
| Symptom | Inspect |
|---|---|
| Quest logic cannot find a visible object | Local child spelling, phase root declaration, and full world registration |
phasePrefabs is populated but nothing exists in world | Block registration, descriptor path, sector, concrete node, and node data |
| Sector loads but one child is unresolved | Full child in nodeRefs, QuestPrefabRefHash, and NodeIndex |
| Parent resolves a child but an external child phase does not | Both dependency locations, the parent/child activation shape, and the child resource; do not assume either inheritance or mandatory duplication |
| Two similar roots behave differently | Compare every segment, delimiter, case, and the descriptor-to-sector pairing; do not rely on visual similarity |
| Behavior differs only on an old save | Inspect facts, journal state, activation history, and any stateful world identity before changing the strings again |
A textual match is necessary but still not runtime evidence. Record the exact installed resources, ArchiveXL registration, clean starting save, and the route exercised before applying Runtime-proven. See Persistent state and Lifecycle, cleanup, and evidence.
Sector nodes and placement
A placed sector object is not represented by one array entry. In the compact
mod-owned arrangement used for the lab, its type lives in nodes[], its
transform and streaming metadata live in nodeData, and its addressable
identity is registered in nodeRefs. Cooked vanilla sectors show that these
collections are not universally parallel, so review their explicit
relationships before assuming a marker or trigger is complete.
Vanilla references
These focused base-game resources are useful comparisons. Extract them from your own installation; they are references, not files to copy into the project.
base\worlds\03_night_city\_compiled\default\quest_606b61008df2ba6f.streamingsector
base\worlds\03_night_city\_compiled\default\always_loaded_0.streamingsector
base\worlds\03_night_city\_compiled\default\exterior_-18_28_0_0.streamingsector
base\worlds\03_night_city\_compiled\default\bd21168eed6c6d62.streamingsector_inplace
The placement triad
| Sector structure | Responsibility |
|---|---|
nodes[index] | Concrete node object and type-specific properties, such as a trigger’s outline and notifiers |
nodeData.Data[*] | Placement and cooked metadata; compact authored sectors use NodeIndex to associate a local concrete node |
nodeRefs[] | Registered NodeRef identities that other resources can address |
A focused conceptual excerpt of that compact shape looks like this:
nodes[3]
$type: worldTriggerAreaNode
...type-specific properties...
nodeData.Data[*]
NodeIndex: 3
Position: Vector4(...)
Orientation: Quaternion(...)
Scale: Vector3(...)
Pivot: Vector3(...)
Bounds: Box(...)
QuestPrefabRefHash: $/mod/.../#root/#trigger
nodeRefs[]
$/mod/.../#root/#trigger
This is a property-focused explanation, not a complete serialized resource. The actual CR2W also contains cooked and versioned data that should be preserved through a supported editor rather than recreated from an abbreviated excerpt.
Do not assume the arrays are parallel
In the compact mod-owned shape and the focused vanilla Quest-sector sample,
NodeIndex associates a placement record with a concrete node. Do not infer
that association from the placement row’s position. More importantly, do not
assume every cooked sector keeps the addressed concrete node in the same
local nodes[] array. nodeRefs[] is a registration set, not a guaranteed
one-for-one mirror of either collection.
The focused vanilla Quest-sector extract happens to contain 21 concrete
nodes, 21 placement records, and 21 registered NodeRefs. Its NodeIndex
values are 0 through 20, and its 21 non-zero QuestPrefabRefHash values
match the registered NodeRef set. A focused vanilla AlwaysLoaded extract has
no local concrete nodes, four placement records, and 32 registered NodeRefs;
its placement indices are 3269, 3273, 3277, and 3291. Those two
arrangements are Observed in vanilla. Separately, a retained mod-owned
comparison has six nodes and four registered references and is
Structurally validated in its prior arrangement. Together they show why
equal counts and local array indices in one file are not schema rules.
Transform and bounds
The retained worldNodeData shape includes:
Positionas aVector4;Orientationas a quaternion;Scale,Pivot, andBounds;NodeIndexandQuestPrefabRefHash;CookedPrefabData,MaxStreamingDistance, andUkHash1;- fields exposed by WolvenKit as
UkFloat1andUk10throughUk14.
QuestPrefabRefHash is exposed as a NodeRef string in WolvenKit JSON despite
its name. Use the field’s observed serialized type and value; do not replace
it with a numeric hash because the property name contains “Hash.”
The Uk* names mark an evidence boundary. Their exact meanings are not
established here. Earlier research treated UkFloat1 as a
streaming-range-like value, but that hypothesis is not enough to rename or
explain the field as a universal engine contract. Preserve values from a
known-good, mod-owned seed or an editor-created resource, and isolate changes.
As an authoring approximation, one world coordinate unit is often treated as roughly one metre. That is Experimental for player-facing distance: HUD rounding, navigation routes, collision, slopes, and vertical geometry can all make the experienced distance differ. Calibrate the actual site in game.
Concrete nodes own behavior-specific data
The sector’s concrete node carries properties unique to its type. The focused
vanilla Quest-sector extract includes 11 worldAISpotNode objects, six
worldTriggerAreaNode objects, three worldSplineNode objects, and one
worldVehicleForbiddenAreaNode. Its trigger nodes include area outlines and
quest trigger notifiers. That inventory is Observed in vanilla only for
the focused extract; it is not a complete catalogue of sector node types.
The later triggers chapter will explain AreaShapeOutline, outline height,
points, and questTriggerNotifier_Quest. The later markers chapter will
separate marker placement from journal map-pin
presentation. Type-specific data belongs on the concrete node while placement and identity remain separate.
Inplace resources are another ownership layer
worldStreamingSector can carry localInplaceResource and
externInplaceResource. An external entry can softly reference a
.streamingsector_inplace, whose worldStreamingSectorInplaceContent owns an
inplaceResources[] collection. In the retained serialized view, the embedded
CR2W bodies themselves are represented in top-level Data.EmbeddedFiles.
Observed in vanilla: the retained
exterior_-18_28_0_0.streamingsector references
bd21168eed6c6d62.streamingsector_inplace. That inplace resource has 45
inplaceResources references: 31 .ent, eight .mesh, and six .xbm. Its
top-level Data.EmbeddedFiles has 43 bodies: 31 entEntityTemplate, six
CMesh, and six CBitmapTexture. The unequal counts are another reason not
to infer array pairing without inspecting the actual handles and paths.
Do not confuse that sector-level resource with a node-local
worldEntityNode.instanceData, which can resolve through
entEntityInstanceData to a RedPackage. They are different owners even when
found during the same world investigation. Questphase inplacePhases is a
third, unrelated property domain; a shared word in the name does not make
these resources interchangeable. These observations are an ownership clue,
not permission to copy the extracted resources.
Placement review procedure
Use WolvenKit’s resource and property views so cooked structures remain under editor control. For each mod-owned addressable node:
- Inspect the concrete
nodes[]object and explain every behavior-specific property the example supplies. - In the compact mod-owned sector, find the placement record whose
NodeIndexis authored to associate with that object. - Verify position, orientation, scale, pivot, and bounds against the intended site.
- Verify
QuestPrefabRefHashis the intended full child NodeRef. - Confirm
nodeRefs[]registers that identity when another resource must address it. - Review local and external inplace-resource ownership independently.
- Save and round-trip the resource, then re-open it and repeat the checks.
- Pack, inspect the archive paths, confirm block registration, and test the node in game from controlled saves.
Do not use a shortened text excerpt as an import template. The downloadable Lab 3 project must contain complete mod-owned resources, and its authoring reference must account for every supplied node and resource.
Boundary Check placement contract
Structurally validated: the canonical resource places the checkpoint
marker at (-1000.02, 1497.2208, 8.3). The reach trigger uses a 25-unit radius,
16 outline points, height 12, and base Z 2.3. The leave trigger uses a
110-unit radius, 20 outline points, height 16, and base Z 0.3. Its finite
padded block box encloses both areas and the intended approach.
After changing the trigger geometry, regenerate and inspect
AreaShapeOutline.buffer together with points and height. Test approach
from several directions, foot crossings, fast travel, streaming return, and
reload before and between the boundaries.
Common failures
| Symptom | Inspect |
|---|---|
| A different concrete node receives the transform | The authored nodeData.NodeIndex association; never rely on placement row order alone |
| Quest lookup fails for one placed node | Full QuestPrefabRefHash, nodeRefs[], descriptor root, and questphase local path |
| Area appears offset or rotated | Position, quaternion orientation, pivot, scale, outline-local points, and Z coverage |
| Behavior changes near the site edge | Descriptor box, node bounds, streaming-distance fields, and test approach direction |
| An entity node exists but a referenced template is missing | Check the separate external/local inplace-resource layer and exact depot path |
| Editor save succeeds but runtime behavior is absent | Round-trip structure, archive contents, ArchiveXL registration, external resources, and runtime logs are separate gates |
| Old saves disagree with clean tests | Saved facts, journal state, checkpoints, and persistent world/device state may survive the archive edit |
For Lab 3 acceptance, use a save created before any version of the quest was installed. Derive separate saves before the reach crossing and after reach has succeeded while the player remains inside the outer leave area. A static placement edit may appear after a new process while the quest still follows old saved facts or journal state; neither observation diagnoses the other. The persistent-state chapter defines that boundary.
Triggers and areas
A trigger objective crosses two resources. A streamed worldTriggerAreaNode
owns the volume and its notifier; a questphase questTriggerCondition asks
about that volume through a NodeRef. Neither side creates the other.
streaming sector
-> worldTriggerAreaNode
-> AreaShapeOutline.buffer
-> questTriggerNotifier_Quest
questphase
-> questTriggerCondition
-> triggerAreaRef
-> Entered / Exited / IsInside / IsOutside
The world node owns the volume
The concrete sector node supplies type-specific behavior. Its compact shape is:
worldTriggerAreaNode
debugName
outline: AreaShapeOutline
buffer
points
height
notifiers[]
questTriggerNotifier_Quest
The sector’s separate nodeData record supplies position, orientation, scale,
bounds, NodeIndex, and the full QuestPrefabRefHash. The outline points are
local to that placement. Moving only the points is not equivalent to moving
the node, and changing only the node position does not resize its outline. See
Sector nodes and placement.
AreaShapeOutline.buffer is authoritative
In the inspected serialized shape, AreaShapeOutline.buffer contains:
little-endian uint32 point count
-> point 0: four little-endian floats (X, Y, Z, W)
-> point 1: four little-endian floats (X, Y, Z, W)
-> ...
-> trailing little-endian float height
The inspected mod-owned outlines use local Vector4 points with W = 1.
WolvenKit can serialize the visible points property as a default square even
when the buffer contains a different polygon. Therefore the visible array is
not sufficient evidence of the effective volume.
For every edit:
- Review the intended local points, point order, and height.
- Regenerate or edit the authoritative buffer through a supported, reproducible authoring path.
- Round-trip the CR2W and verify the buffer still decodes to the intended count, points, and height.
- Review the node transform and vertical placement separately.
- Test crossings from several directions and elevations in game.
Do not paste a buffer from a different location simply because its visible
points look plausible. The count, point payload, height, transform, bounds,
and intended approach form one geometry contract.
The notifier exposes the area to quest logic
The focused Quest-sector sample uses questTriggerNotifier_Quest on trigger
nodes. Retained values include isEnabled, includeChannels, and
excludeChannels; one inspected quest-notifier shape uses TC_Default for
the included channels. Those values are Observed in vanilla, not a promise
that every trigger family or activator uses the same channel configuration.
An outline without the intended notifier may be valid geometry but invisible to the quest condition. Conversely, a condition with a valid-looking NodeRef does not attach a notifier to the world node. Inspect both owners.
The condition owns the question
questTriggerCondition carries four decisive properties in the retained
shape:
| Property | Responsibility |
|---|---|
type | Selects edge-shaped or state-shaped trigger semantics |
triggerAreaRef | Local NodeRef of the area being queried |
activatorRef | Explicit activator identity where the authored shape uses one |
isPlayerActivator | Selects player activation in the inspected player-targeted shape |
A focused Observed in vanilla condition excerpt is:
questTriggerCondition
activatorRef: gameEntityReference
reference: 0
type: EntityRef
isPlayerActivator: 1
triggerAreaRef: #q108_tr_mainframe
type: IsInside
The zero reference and isPlayerActivator: 1 belong to that inspected player
condition. Do not replace an empty, local, or explicit activator reference
from a known-good comparison without understanding that comparison’s context.
Edge and state conditions are not synonyms
type | Question asked | Important test boundary |
|---|---|---|
Entered | Did the activator cross from outside to inside? | An already-inside reload may not produce a new entry edge. |
Exited | Did the activator cross from inside to outside? | Starting outside is not the same event as leaving. |
IsInside | Is the activator currently inside? | Suitable for a state check, but reload evaluation still needs runtime evidence. |
IsOutside | Is the activator currently outside? | Can already be true before the intended sequence unless graph order constrains it. |
The retained enum also exposes AllInsideMP and AllOutsideMP. Their names
are not enough to teach multiplayer semantics, so this chapter records their
existence without prescribing them.
Choose semantics from the player story. “Cross the threshold now” is an edge.
“Proceed whenever the player is already in the area” is a state. A leave step
must activate only after the reach step; otherwise IsOutside can satisfy
before the player ever enters.
Boundary Check trigger contract
Structurally validated: Lab 3 uses two independently placed areas beneath one quest-prefab root:
| Area | Condition | Encoded geometry |
|---|---|---|
#cqa003_tr_reach | IsInside | 25-unit radius, 16 points, height 12 |
#cqa003_tr_leave | IsOutside | 110-unit radius, 20 points, height 16 |
The graph activates the reach objective and waits for IsInside; only after
that succeeds does it activate the leave objective and wait for IsOutside.
The larger outer area prevents an ordinary step back across the reach boundary
from completing both stages at once. The resource values and graph order are
checked; vertical coverage, state evaluation, and approach behavior remain
Experimental acceptance questions.
Save-aware acceptance matrix
Use a save made before any version of the mod was installed. Retain separate save slots; do not keep overwriting the only clean control.
| Case | What it distinguishes |
|---|---|
| Start outside both areas | Negative control for premature IsInside success |
| Walk into the reach area | Normal state or edge activation path |
| Save before reaching, then reload | Quest and journal state restoration before a crossing |
| After reach succeeds, save inside the outer leave area and reload | The active IsOutside wait stays false until departure |
| Leave the outer area | Ordered leave behavior and vertical/outline coverage |
| Reload after completion | One-shot fact, journal cleanup, and stale trigger state |
| Reinstall and use an old save | Demonstrates why archive replacement does not reset save-backed state |
This matrix does not load a save while the active reach wait is already inside
#cqa003_tr_reach. It therefore does not prove IsInside evaluation for that
on-load state; add a separately bound case before making that claim.
Record the exact installed archive, resource paths, starting save history, approach direction, player Z, observed objective transition, and relevant ArchiveXL/game logs. A successful pack or WolvenKit open cannot support Runtime-proven by itself.
Common failures
| Symptom | Inspect first |
|---|---|
| Condition never changes | Trigger notifier, local/full NodeRef chain, sector registration, and activator selection |
| Trigger fires at a default square | Authoritative AreaShapeOutline.buffer, not only visible points |
| Trigger is offset | Node placement, outline-local coordinates, pivot, orientation, and scale |
| Enter works but an inside reload stalls | Entered versus IsInside, then retained runtime evidence for that exact graph |
| Leave succeeds before reach | Graph activation order and premature IsOutside evaluation |
| Behavior differs by elevation | Base Z, trailing buffer height, placement bounds, and real site geometry |
| A clean run works but an old save does not | Facts, journal state, checkpoints, and prior trigger lifecycle are separate saved variables |
Previous: Sector nodes and placement. Next: Markers and navigation.
Markers and navigation
A marker, a quest pin, and a GPS route are related, but they are not one resource. The world supplies an addressable anchor; the journal supplies UI intent; a questphase changes journal state; the navigation system decides whether a usable route exists.
worldStaticMarkerNode + placement + registered NodeRef
<- gameJournalQuestMapPin.reference.reference
<- questMappinManagerNodeDefinition Active / Inactive
-> HUD icon, map pin, and possible GPS route
Vanilla references
These focused base-game resources are useful comparisons. Extract them from your own installation; they are references, not files to copy into the project.
base\worlds\03_night_city\_compiled\default\always_loaded_0.streamingsector
base\journal\cooked_journal.journal
base\quest\main_quests\part1\q108\phases\q108_06b_tower_mainframe.questphase
base\gameplay\devices\drop_points\drop_point.ent
Four records with different jobs
| Record | Owner | What it does not prove |
|---|---|---|
worldStaticMarkerNode | Streaming sector | It does not create a journal entry or activate UI. |
gameJournalQuestMapPin | Journal resource | It does not place or stream its target. |
questMappinManagerNodeDefinition | Questphase | Its Out signal does not prove the HUD redrew or GPS found a route. |
scnWorldMarker | Scene/quest scene-location payload | It is not a substitute for a quest journal pin. |
The shared word “marker” describes several identifier consumers, not one interchangeable type.
worldStaticMarkerNode is the anchor
The concrete node is intentionally small:
nodes[index]
worldStaticMarkerNode
debugName
nodeData[...]
NodeIndex: index
Position / Orientation / Scale / Bounds
QuestPrefabRefHash: $/.../#root/#marker
nodeRefs[]
$/.../#root/#marker
The node’s placement determines the world anchor. Its full NodeRef is registered world-side; a questphase or journal normally uses the corresponding local reference beneath an available prefab root. A visible debug name is not the identity and does not compensate for a mismatched NodeRef.
Observed in vanilla: retained AlwaysLoaded sectors register static-marker
NodeRefs in arrangements that are not always one-for-one with local concrete
nodes. Therefore the compact mod-owned shape above is useful for authoring,
but equal nodes, nodeData, and nodeRefs counts are not a schema rule.
The journal entry owns presentation intent
gameJournalQuestMapPin owns the player-facing map-pin data:
| Property | Responsibility |
|---|---|
reference.reference | Target NodeRef |
enableGPS | Allows routing participation; it does not guarantee a route |
mappinData.mappinType | Mappin definition TweakDBID |
mappinData.variant | Visual role, such as DefaultQuestVariant in the cited shape |
mappinData.localizedCaption.value | Onscreen localization key |
reference.slotName | Optional slot exposed by an entity target |
offset | Local display offset from the resolved target |
The focused vanilla journal includes local marker references beneath quest
objectives. Extract base\journal\cooked_journal.journal and inspect the
small containing quest subtree instead of copying the full resource. The
complete journal lifecycle is in Mappins: journal intent meets the
world.
The quest node owns activation and cleanup
questMappinManagerNodeDefinition addresses a typed gameJournalPath. Its
incoming Active or Inactive socket selects the requested state.
disablePreviousMappins is a transition policy, not the activation switch.
objective Active
-> mappin manager through Active
-> wait for world condition
-> mappin manager through Inactive
-> objective Succeeded
Use disablePreviousMappins: 0 by default. When replacing a destination leaves
a stale dotted route, test 1 on the new pin’s activation. Check that other
objectives keep their intended routing.
scnWorldMarker belongs to scene location semantics
Focused vanilla questphase extracts contain scnWorldMarker values inside
scene-related payloads, with a nodeRef selecting a scene location. This is a
different record from the streamed worldStaticMarkerNode and from a journal
pin. A scene may ultimately resolve a world marker NodeRef, but its scene
payload does not acquire journal caption, GPS, or quest-pin lifecycle merely
because it contains the word “marker.”
Keep distinct NodeRefs when scene placement and quest UI have different ownership or lifetime needs. Do not point a journal pin at a scene marker simply because that marker already exists without testing load scope and cleanup.
Slots are template-specific
An entity target can expose authored slots. Observed in vanilla: the
drop_point.ent template has UI and navigation-related slots at distinct
local transforms, including UI_Interaction, poi_mappin, roleMappin, and
main_slot/navQuery. The exact names and offsets belong to that template.
A static marker has no automatic knowledge of those entity-local slots. If a
mod-owned marker is used instead of the device NodeRef, calculate and test the
intended world position explicitly. Never generalize UI_Interaction or
main_slot/navQuery to unrelated templates; inspect the actual .ent and its
component hierarchy.
GPS is an outcome, not a field
A route depends on more than enableGPS:
active tracked objective
+ active quest mappin
+ resolvable loaded target
+ suitable endpoint near traversable space
+ navigation state
-> possible route
Pin visibility, map visibility, a yellow world icon, and a complete drivable or walkable route are separate observations. Test each one and record the endpoint. A route to the marker origin can be wrong even when the icon appears correctly above the target.
worldStaticGpsLocationEntranceMarkerNode also occurs in retained
world-location research. That is a narrow Observed in vanilla node-type
observation, not evidence that every quest pin needs an entrance marker or
that adding one repairs arbitrary GPS failures. Location systems, quest pins,
and entity navigation slots must be investigated in their actual owning
resources.
Boundary Check marker contract
Structurally validated: Lab 3 has an AlwaysLoaded static marker named
#cqa003_mp_checkpoint at (-1000.02, 1497.2208, 8.3). Its journal pin uses
DefaultQuestVariant, GPS enabled, and a unique localized caption. The quest
graph activates the pin before waiting for the reach area and deactivates it
after reach succeeds.
Both manager nodes use disablePreviousMappins: 0. The lab has no deliberate
previous route to replace, so the Active and Inactive sockets alone own the
requested transition. The separate retained replacement-route result above
does not justify setting this isolated activation to 1.
The marker’s category does not guarantee UI resolution, and the coordinate does not guarantee a sensible route endpoint. Acceptance must cover target resolution before approach, icon height, map presentation, route endpoint, ordinary walking, deactivation, save/reload while active, and completed-state reload.
Common failures
| Symptom | Inspect first |
|---|---|
| No pin, but the marker exists | Journal path, mappin manager socket, journal registration, and saved journal state |
| Pin appears at the origin or wrong place | reference.reference, full/local NodeRef chain, target load scope, slot, and offset |
| Icon is too low or high | Marker transform versus entity slot transform and journal offset |
| Pin appears but no GPS route exists | Tracking state, enableGPS, target resolution, route endpoint, and nearby traversable space |
| A stale dotted route remains | Explicit prior inactivation and a focused disablePreviousMappins replacement test |
| A scene starts but the quest pin fails | scnWorldMarker and journal mappin are separate records; inspect both owners |
| Old saves disagree with clean runs | Journal activation, tracking, routes, facts, and checkpoints may all be save-backed |
Previous: Triggers and areas. Next: Devices and persistence.
Devices and persistence
A placed device is a resource chain, not merely a mesh with a NodeRef. The world node selects an entity template and placement; the template supplies components and slots; optional node-local instance data can override controllers; quest logic addresses an identity; save data can retain the result after the archive changes.
worldEntityNode or worldDeviceNode
-> entityTemplate (*.ent)
-> optional instanceData
-> entEntityInstanceData
-> RedPackage controller/component chunks
-> world placement and NodeRef
-> context-dependent global device/persistence resources
Vanilla references
These focused base-game resources are useful comparisons. Extract them from your own installation; they are references, not files to copy into the project.
base\worlds\03_night_city\_compiled\default\exterior_19_-8_0_0.streamingsector
base\worlds\03_night_city\_compiled\default\4fd0915183681e53.streamingsector_inplace
base\gameplay\devices\drop_points\drop_point.ent
base\worlds\03_night_city\_compiled\default\03_night_city.devices
worldEntityNode and worldDeviceNode
Retained vanilla sectors contain both types. Treat the serialized node type as evidence; do not classify a placement solely from its visible appearance or the filename of its entity template.
| Concern | worldEntityNode | worldDeviceNode |
|---|---|---|
| Entity-template placement | Yes, in observed contexts | Yes, in observed contexts |
Node-local instanceData | Present in observed entity examples | Present in mod-owned and vanilla device research shapes |
| Device-specific world fields | Not established by the generic type name | Retained shapes expose fields such as device class, alpha-hack streaming override, and entity streaming properties |
| Controller behavior | Comes from the template and/or bound package, not the node name alone | Comes from the template and/or bound package, not the node name alone |
A focused placement excerpt is:
worldEntityNode or worldDeviceNode
entityTemplate: base\...\template.ent
instanceData
entEntityInstanceData
buffer: RedPackage
worldNodeData
Position / Orientation / Scale
QuestPrefabRefHash: full device NodeRef
Do not raw-copy an unrelated vanilla RedPackage. Controller chunks can
contain template-coupled component IDs, quest state, messages, actions, and
other instance-specific data.
The entity template owns components and slots
The .ent supplies controller components, scanner components, appearances,
interaction components, workspots, and attachment slots. Which of these
exists is template-specific.
Observed in vanilla: base\gameplay\devices\drop_points\drop_point.ent
contains distinct UI and approach positions. Its retained slot investigation
found UI_Interaction, poi_mappin, roleMappin, and
main_slot/navQuery at different local offsets. That explains one drop-point
layout; it is not a standard slot set for computers, doors, access points, or
vending machines.
When a quest needs a slot:
- Inspect the exact entity template and component hierarchy.
- Record the component transform and slot-local transform.
- Apply the owning entity’s world transform.
- Test interaction, icon height, and navigation endpoint separately.
A slotName written into a quest or journal reference is not proof that the
target template exposes that slot.
NodeRef forms depend on context
The retained resources show several forms:
| Form | Observed context | Example |
|---|---|---|
| Local child | Questphase device-manager target beneath available prefab dependencies | #q108_dvc_door_to_soulkiller |
| Local device-prefab root | A phase-node dependency that makes a device namespace available | #loc_sq021_trailer_park_devices |
| Local device target | Quest logic in that inspected location context | #sq021_randy_pc |
| Absolute world identity | Cross-resource world/device lookup and sector registration | The full SQ021 path below |
The retained SQ021 laptop identity is:
$/03_night_city/se1/#loc_sq021_trailer_park/
loc_sq021_trailer_park_gameplay_prefabV4S2BNI/
#loc_sq021_trailer_park_devices/#sq021_randy_pc
These are Observed in vanilla examples, not string-rewrite rules. A local target is resolved in its prefab context; an absolute reference carries the world hierarchy. Do not mechanically concatenate a local root and child or shorten an absolute native device reference without verifying the owner that establishes resolution.
Device-relative identities can also appear beneath a location’s device-prefab root, as the SQ021 chain demonstrates. The root, target, phase dependency, sector registration, and concrete placement must agree. Review Quest prefabs and NodeRefs before authoring a custom target.
RedPackage is not an inplace sector
The retained SQ021 laptop chain provides a useful ownership boundary:
exterior_19_-8_0_0.streamingsector
worldEntityNode
instanceData -> entEntityInstanceData -> RedPackage
nodeData.CookedPrefabData
-> 4fd0915183681e53.streamingsector_inplace
-> embedded entity templates
The node-local RedPackage contains controller/component chunks for that placed
entity. The .streamingsector_inplace owns embedded resources used by the
sector. They are separate even when the same placement refers to both. A
sector’s localInplaceResource or externInplaceResource is a third property
location; questphase inplacePhases is unrelated again.
In the inspected laptop, component CRUIDs had to match the controller and scanner components from its template for the authored controller data to bind. Arbitrary replacement IDs left a visible, usable laptop but selected default empty Files content. That is a bounded Runtime-proven result for that template/package, not permission to reuse its IDs or payload elsewhere.
.devices and .psrep are context-dependent
Do not teach either resource as universally mandatory or universally irrelevant.
Observed in vanilla: the inspected SQ021 laptop’s NodeRef hash,
node-data ID, and component CRUIDs were absent from the searched global
03_night_city.devices, 03_night_city_init.devices, and direct values in
03_night_city.psrep. Its Files UI came from node-local instance data, so
those global resources were not prerequisites for that behavior.
In a different mod-owned research case, a sparse .devices entry was needed
as the candidate registration path for reliable quest-side controller lookup.
A .psrep patch remained optional until persistence evidence justified it.
This contrast is the rule: begin with the native comparison for the exact
device and operation, then add the smallest resource whose absence is actually
demonstrated.
| Requirement | Evidence to collect |
|---|---|
| Visible entity | Sector registration, node placement, entity template, appearance |
| Interaction | Template components, slots/workspots, controller binding |
| Quest device-manager action | Exact NodeRef context, controller class, lookup/registry behavior |
| Persisted controller state | Fresh-save and reload matrix, then .psrep or other persistence evidence if required |
One success does not prove the next row.
Device identity is save-sensitive
Device persistent state can survive in the save after a package is removed or changed. A retained test save continued to expose earlier copied content after the authored arrays had been cleared. Repacking the same NodeRef therefore did not create a clean experiment.
Use a new NodeRef when materially changing a test device’s controller shape, or return to a save that never streamed that identity. Keep the old and new identities out of the same acceptance run. Resetting a quest fact does not erase device controller state, journal state, or a streamed checkpoint.
Device research procedure
For a chosen vanilla comparison:
- Record its full NodeRef, source sector,
NodeIndex, transform, and node type. - Inspect its exact entity-template path and appearance mapping.
- Inspect template components, CRUIDs, workspots, and relevant slots.
- Determine whether node-local
instanceDataexists and inventory its RedPackage chunks without copying the whole payload. - Follow sector inplace references separately.
- Search
.devicesand.psrepfor the exact identity, but treat absence and presence as evidence for that device only. - Inspect the quest node or condition that addresses it and record whether the NodeRef is local or absolute in that context.
- Build a mod-owned minimal fixture and test visibility, interaction, quest action, persistence, cleanup, and replay as separate cases.
Never patch a shared vanilla entity template merely to change one placed device. Prefer a mod-owned clone or a mod-owned placement with an explicitly documented dependency chain.
Common failures
| Symptom | Inspect first |
|---|---|
| Device is visible but action data is missing | Template/component binding and node-local RedPackage CRUIDs |
| Journal icon appears at the wrong height | Exact template slot and transform, not a presumed universal slot |
| Quest device-manager node cannot find a usable device | Local/absolute NodeRef context, prefab dependency, controller class, and .devices evidence |
Adding .devices changes nothing | Verify that the failed behavior actually consumes that registry and that the sparse patch merged |
| State returns after removing an override | Save-backed device identity; use a clean save or fresh NodeRef |
| Inplace extraction lacks controller data | Check node-local instanceData; do not conflate RedPackage with .streamingsector_inplace |
| A copied controller works on one template only | Template-coupled components, slots, and CRUIDs were copied as hidden assumptions |
Previous: Markers and navigation. Next: Advanced devices, interactions, and persistent state.
Advanced devices, interactions, and persistent state
A quest-controlled device is a collaboration among a placed world entity, an entity template, bound controller data, quest logic, and save state. No single resource owns the whole interaction. This chapter turns that split into an authoring and test contract for computers, access points, doors, drop points, and other controller-backed world objects.
The outcome is an inspectable, mod-owned device experiment in which you can name:
- the resource that places the device;
- the component and controller data that make the interaction available;
- the NodeRef and controller class used by quest logic;
- the signal that proves success, failure, or cancellation;
- the final state expected after completion, reload, and removal.
This is an advanced research workflow, not a universal device template. The current labs do not supply a custom device checkpoint, and a new device remains Experimental until its own clean-save matrix passes.
Vanilla references
These focused base-game resources are useful comparisons. Extract them from your own installation; they are references, not files to copy into the project.
base\worlds\03_night_city\_compiled\default\exterior_19_-8_0_0.streamingsector
base\worlds\03_night_city\_compiled\default\4fd0915183681e53.streamingsector_inplace
base\gameplay\devices\masters\computers\laptop_1.ent
base\worlds\03_night_city\_compiled\default\03_night_city.devices
base\quest\side_quests\sq021\phases\sq021_randys_room.questphase
base\quest\main_quests\part1\q108\phases\q108_06b_tower_mainframe.questphase
The ownership chain
Treat the device as a chain of owners rather than one object:
streaming block and sector
-> worldEntityNode or worldDeviceNode
-> full NodeRef + placement
-> entityTemplate (.ent)
-> controller / scanner / interaction components
-> slots and workspots
-> optional node-local entEntityInstanceData
-> RedPackage controller/component chunks
questphase
-> prefab dependency and local/absolute NodeRef context
-> optional quest-issued controller action
-> player-driven interaction or engine event
-> controller condition, fact, or journal signal
-> explicit outcome and owner-specific final state
save
-> facts, journal, active graph, and device persistent state
| Owner | What it decides | What it cannot prove alone |
|---|---|---|
| Streaming sector | Concrete node, transform, registered NodeRef, and node-local instance data | That the interaction is reachable or the quest can resolve it |
| Entity template | Components, controller identities, appearances, slots, and workspots available to the entity | That copied instance data binds or that a particular action string is accepted |
Node-local RedPackage | Instance-specific controller/component state in observed devices | That global registry or save behavior is unnecessary elsewhere |
.devices / .psrep family | Context-dependent lookup and persistence data | A universal prerequisite for every Files UI or quest operation |
| Questphase | When to issue a command, what signal to observe, and how to advance | Creation of the entity, prompt, animation, or persistent controller implementation |
| Journal/facts | Player-facing and durable quest outcomes | Automatic device reset or removal |
| Save | Previously materialized graph and persistent state | That the currently installed archive still matches the saved topology |
The owner table is also a failure-isolation map. A visible prop with an empty Files tab points toward component/package binding. A functioning prompt that never advances the quest points toward the observation signal. Behavior that changes only on an old save points toward persistent identity.
Three joins must agree
World identity join
The sector registers the concrete full NodeRef. A phase may address a local
child only when its quest-prefab dependencies establish the surrounding
namespace. Copying the final #name without the owner chain does not recreate
the identity.
The retained SQ021 laptop uses the full path:
$/03_night_city/se1/#loc_sq021_trailer_park/loc_sq021_trailer_park_gameplay_prefabV4S2BNI/#loc_sq021_trailer_park_devices/#sq021_randy_pc
Its phase can use a shorter local device reference because another resource establishes that context. This is Observed in vanilla, not a string shortening rule for a mod-owned prefab.
Component-binding join
The SQ021 placement contains node-local controller data with CRUIDs that match
components in laptop_1.ent. In the retained comparison, the active computer
controller uses 1131680419258347532 and the scanning component uses
1131680419258347552.
Match component bindings to the selected entity template. If the laptop is visible but its authored content is empty, inspect the instance package and component IDs before changing the document resources. SQ021’s IDs are specific to its template.
Keep the separate inplace link separate as well. The laptop placement’s
CookedPrefabData points at the cited .streamingsector_inplace, while its
controller chunks live in node-local instanceData. Extracting one does not
substitute for inspecting the other.
Quest/controller join
The quest payload must agree with the controller actually bound to the placed entity. Two native shapes cover the common command/observation split:
| Purpose | Native shape | Decisive fields |
|---|---|---|
| Issue a quest-owned command | questInteractiveObjectManagerNodeDefinition -> questDeviceManager_NodeType -> questDeviceManager_NodeTypeParams | objectRef, slotName, entityRef, deviceControllerClass, deviceAction, actionProperties |
| Observe controller state | questPauseConditionNodeDefinition -> questObjectCondition -> questDevice_ConditionType | objectRef, deviceControllerClass, deviceConditionFunction, functionParameters |
WolvenKit’s generated types and focused serialized resources establish those field names as Structurally validated. They do not establish a public list of portable action or function strings.
Commands and observations are different edges
Use a manager node only when the quest owns the command. If the requirement is “the player uses the device,” let the asset expose its prompt and wait on the resulting controller state, fact, or journal signal.
quest-driven route
configure or force exact controller action
-> observe authoritative resulting state
-> record outcome
player-driven route
make asset eligible, if required
-> player selects asset-owned prompt
-> observe authoritative result
-> record outcome
Sending the completion action immediately before waiting for it can satisfy the graph without player input. Conversely, waiting on a plausible function name does not create an interaction the template never exposed.
Observed in vanilla: the cited Q108 phase sends ForceDisabled to
#q108_dvc_door_to_soulkiller and ForceCloseImmediate to
#q108_dvc_door_to_mainframe_interior, both with DoorControllerPS. The same
resource establishes only those exact bindings in that quest context. A
generic ForceOpen, lock, restore, or obstruction policy remains
Experimental for a newly chosen door.
Another focused vanilla comparison in
base\open_world\street_stories\watson\northside_industrial_district\sts_wat_nid_03\phases\sts_wat_nid_03_openworld.questphase
waits on DoorControllerPS.IsOpen for #nid_03_dvc_hwangbo_room_door. This
is Observed in vanilla evidence for an action/condition separation, not a
promise that every door reports that function identically across reload,
obstruction, or streaming.
Interaction result ownership
Choose a completion signal produced by the interaction itself:
| Interaction family | Candidate authoritative signal | Boundary |
|---|---|---|
| Journal-backed computer file | The exact gameJournalFile.questInfo.factName written when that file opens | Proves that configured file signal only, not that the player read every line or visited another tab |
| Personal-link action | Evidence-matched controller function such as the tested ScriptableDeviceComponentPS.IsPersonalLinkConnected, followed by the authored operation and disconnect | The tested route proves its exact package; success, cancel, and disconnect are separate states |
| Quest-forced door state | Evidence-matched controller condition or a downstream traversal fact/trigger | Issuing the manager command is not proof that collision, navigation, or companions traversed safely |
| Drop point | Engine-owned deposit fact after the exact reservation event and item handoff | A reservation request is not proof of item removal or kiosk accessibility |
| Generic prompt | Controller state, explicit quest fact, journal signal, or later world condition bound to that prompt | Prompt visibility or animation start is not completion |
The SQ021 file route demonstrates the first row. Its controller supplies the
Files content; the file owns sq021_randy_pc_file_cartoon; the phase waits for
that fact, coordinates its UI/scene response, then clears the transient signal.
The scene does not create the file. The quest does not infer a click from the
laptop being open.
Model cancellation explicitly
“Cancel the interaction” can describe several unrelated events:
| Event | Possible owner | Required design decision |
|---|---|---|
| Player closes a device UI | Device/UI controller | Is the operation retriable, and which state proves the UI actually closed? |
| Player breaks a personal-link interaction | Device/workspot/controller | Is a disconnect action required, and are items or facts unchanged? |
| Quest branch becomes irrelevant | Quest graph | Is the waiting signal made harmless, or does a separately tested cut target it? |
| Scene using the same entity interrupts | Scene plus quest wrapper | Which named interruption/return outcome retains or releases the device? |
| Device streams out | World streaming plus persistent controller | Does the interaction resume, fail, or require the player to approach again? |
| Quest completes or is removed | Root lifecycle and save | Should the controller preserve its final state, restore a baseline, or be left to its native owner? |
There is no generic device-cancel node established by the current evidence.
Do not treat a phase output, logical XOR, UI close, or CutDestination as an
automatic controller rollback. Record the cancellation discriminator before
any irreversible inventory or journal write, then route retry and terminal
failure separately.
Test connection, timed presentation, forced disconnect, item consumption, and completion in order. Repeat with an early disconnect and reload while connected.
Persistent state is part of the public identity
A device NodeRef can identify persistent state saved by an earlier build. Changing authored content under the same identity may leave old content in that save. Retest from a save made before the device was installed or streamed.
Treat these as independent saved surfaces:
- the active questphase node and checkpoint;
- facts written by device content or quest logic;
- journal file/message state and visited state;
- device controller persistent state;
- active UI, scene, or workspot state where the game permits saving;
- the world identity that associates the save with the placed device.
Use a new mod-owned NodeRef when materially changing the controller/package shape during research, or load a save that never streamed the old identity. Do not combine old and new identities in one positive acceptance run. Resetting one fact does not clear the other surfaces.
Do not add a .devices or .psrep contribution by superstition. The SQ021
laptop’s searched identity was absent from the cited global .devices
resource, yet its Files UI came from node-local instance data. Other
quest-manager lookups can have different requirements. Begin from a focused
native comparison for the same controller and operation, then add the smallest
resource whose absence your experiment actually demonstrates.
Author the experiment in WolvenKit
Use a new mod-owned identity and keep the first candidate deliberately small:
- Create or copy a mod-owned WolvenKit project; do not edit an installed lab or a base-game resource in place.
- Extract the exact vanilla comparison from your own game and record its
sector, node type, entity-template path, full NodeRef, transform, and
instanceDatapresence. - Inspect the entity template’s controller, scanner, interaction, slot, and workspot components. Record their names and CRUIDs before editing the placement package.
- Add the mod-owned sector node, placement, and full NodeRef. Verify that the quest-prefab root and every local child reference resolve to that identity.
- Author only the controller/component chunks required by the comparison. Preserve deliberate template bindings; remove copied quest content and scanner clues that your experiment does not own.
- Add
.devicesor other persistence resources only when the exact native comparison or a controlled missing-resource test requires them. - In the questphase, activate presentation first. Add a manager action only if the quest owns the command; otherwise proceed directly to the player interaction and observation gate.
- Build the condition with the concrete
questDevice_ConditionType, exact controller class, exact NodeRef context, function, and parameters. A fact or journal signal can replace the controller condition when it is the native content’s authoritative result. - Route success, retryable cancellation, and terminal failure before adding item removal, journal completion, or a durable completion fact.
- Define the final controller state for normal completion, interruption, failure, reload, stream return, and archive removal. Do not invent a restore action merely to make the graph symmetrical.
- Save, close, reopen, and serialize the resources. Inspect concrete types,
non-null handles, component IDs, NodeRefs, controller
CNamevalues, function/action arrays, sockets, and all world registration paths. - Cook and pack only mod-owned files. Extract the finished archive and compare its payload inventory and hashes with the candidate you intend to test.
The supplied labs remain useful ownership references, but none is a custom device download. The manual experiment above remains Experimental until the complete matrix below is retained for its exact archive.
Clean-save and lifecycle matrix
Start the identity/topology campaign from a save created before the device was ever installed or streamed. Record the installed archive name, project commit, exact save, versions, route, controller state, quest/journal state, and logs for every case.
| Case | Required observation |
|---|---|
| Fresh approach | Entity appears once; prompt, workspot, slots, and navigation are usable at the final transform |
| Normal success | Player-driven input is required when promised; authoritative signal occurs once; inventory/journal/facts advance in the authored order |
| Cancel before commitment | No success fact, item consumption, or objective success; UI/workspot/controller releases; retry remains possible |
| Interrupt during operation | The authored failure, return, or retry policy occurs without duplicate effects or a stuck player/actor |
| Reload before interaction | Prompt and objective restore without auto-completing |
| Reload during a supported interaction state | Controller, UI/workspot, and quest state recover according to the recorded policy; if saving is unavailable, record that limitation |
| Reload after success | Final state and progression remain coherent; the action does not replay |
| Stream away and return | Device resolves once and preserves or re-evaluates exactly the state the design claims |
| Repeated use | One-shot content cannot duplicate a reward or item mutation; repeatable content follows its explicit reset policy |
| Old-save negative control | Previously streamed identity is kept out of positive evidence or is documented as intentionally migrated |
| Removal isolation | Removing the archive from a copied save does not support claims about reset; record lingering save-backed state separately |
| Second untouched save | The exact candidate and route reproduce without inherited controller, fact, journal, or graph history |
Failure routing
| Symptom | Inspect first |
|---|---|
| Device appears but authored content is absent | Entity-template component names/CRUIDs and node-local RedPackage binding |
| Prompt is absent | Interaction component/TweakDB setup, workspot and slot ownership, controller eligibility, placement clearance |
| Quest command does nothing | objectRef context, controller class, action name/properties, prefab dependency, and evidence for registry lookup |
| Player can interact but objective never advances | Whether the condition/fact belongs to that exact interaction result and whether the signal was already true |
| Objective advances without input | A quest-issued action performed the promised player operation or an old save retained the completion state |
| Cancel consumes the item or writes success | Commitment writes occur before the authoritative result or cancel and success converge too early |
| Door moves but traversal fails | Collision, navigation, obstruction, companions, and the concrete final controller state |
| Old content returns after repacking | Reused NodeRef and save-backed controller state; use an untouched save or fresh experimental identity |
Added .devices/.psrep changes nothing | The failed behavior may not consume that resource; return to the exact native owner chain |
Previous: Devices and persistence. Next: Location research.
Location research
Choose a location by inspecting its world resources and visiting it in game. Build a small mod-owned test project rather than copying a vanilla sector.
Retained comparison paths
These depot paths form a focused research route. Extract them from your own game with WolvenKit. Do not commit or distribute the extracted CR2W files.
| Depot path | Question it can answer |
|---|---|
base\worlds\03_night_city\_compiled\default\03_night_city.streamingworld | Which global resources and compiled block list the native world root references |
base\worlds\03_night_city\_compiled\default\blocks\all.streamingblock | Which descriptor points to a sector, with which category, level, box, and quest-prefab root |
base\worlds\03_night_city\_compiled\default\quest_606b61008df2ba6f.streamingsector | How one focused Quest sector relates nodes, placements, NodeRefs, triggers, and a quest-prefab namespace |
base\worlds\03_night_city\_compiled\default\always_loaded_0.streamingsector | How one AlwaysLoaded sector registers references and marker-related content without assuming compact array pairing |
base\worlds\03_night_city\_compiled\default\exterior_-17_22_0_0.streamingsector | Where the vanilla Allen Street fast-travel terminal is placed and which entity template it uses |
base\worlds\03_night_city\_compiled\default\always_loaded_1.streamingsector | Where that terminal’s linked fast-travel destination marker is registered and placed |
base\localization\en-us\onscreens\onscreens.json | Which player-facing English label is assigned to the terminal record’s localization key |
base\quest\main_quests\part1\q108\phases\q108_06b_tower_mainframe.questphase | How one vanilla phase declares prefab dependencies and uses local trigger, device, scene-marker, AI-spot, and spline references |
base\journal\cooked_journal.journal | How quest mappins and their local world references are represented in the journal tree |
base\worlds\03_night_city\_compiled\default\exterior_19_-8_0_0.streamingsector | How one placed laptop is linked to node data, an entity template, node-local instance data, and cooked prefab data |
base\worlds\03_night_city\_compiled\default\4fd0915183681e53.streamingsector_inplace | What that placement’s separate inplace resource embeds—and what it does not own |
base\gameplay\devices\drop_points\drop_point.ent | Which components and local UI/navigation slots one drop-point template exposes |
base\worlds\03_night_city\_compiled\default\03_night_city.devices | Whether a searched device identity appears in one global device registry |
Each row is a retained Observed in vanilla research target, not a complete template or a guarantee that the same relationship applies to another location.
Start from the player-facing site
Before extracting resources, write down the intended behavior:
- safe and legal player access at the quest’s intended time;
- on-foot and, if relevant, vehicle approach;
- approximate player/world coordinates and vertical level;
- nearby doors, elevators, interiors, restricted areas, traffic, and combat;
- a sensible marker and navigation endpoint;
- enough space for the trigger outline and its test approaches;
- expected behavior after fast travel and active-state reload, plus whether a completed-save reload stays inactive.
Take your own reference screenshots for private research if useful, but explain the published model with coordinates, conceptual diagrams, property tables, and mod-owned resources. Do not use WolvenKit graph screenshots as the chapter’s graph explanation.
Boundary Check worked location record
Lab 3 centers its mod-owned marker and two trigger outlines on the outdoor
recycling-station cabinet row in Watson/Kabuki at
(-1000.02, 1497.2208, 8.3).
Observed in vanilla: extracting
base\worlds\03_night_city\_compiled\default\exterior_-17_22_0_0.streamingsector
from your own game shows the fast-travel terminal entity at node index 1766:
| Field | Focused value |
|---|---|
| TweakDB record | FastTravelPoints.wat_kab_dataterm_12 |
| Full NodeRef | $/03_night_city/c_watson/kabuki/kabuki_data_terminals_prefabW7DFTCQ/#wat_kab_dataterm_12/{kab_data_term_}12_prefabBARKVWA |
| Entity template | base\gameplay\devices\fast_travel\data_term_1.ent |
| Entity position | (-1064.30457, 1436.18298, 4.95000076) |
Its point record resolves through the installed r6\cache\tweakdb.bin to
displayName: LocKey#79217; the English onscreens.json maps key 79217 to
Allen Street. The linked destination is Observed in vanilla at node
index 5757 in always_loaded_1.streamingsector:
| Field | Focused value |
|---|---|
| Marker NodeRef | $/03_night_city/c_watson/kabuki/kabuki_data_terminals_prefabW7DFTCQ/#wat_kab_dataterm_12/#wat_kab_dataterm_12_tp |
| Marker position | (-1065.76489, 1435.83472, 4.94000006) |
The entity is about 88.6 m and the linked marker about 89.95 m horizontally
southwest of the lab center. A calculation against the 20-sided 110 m leave
outline leaves about 18.8 m of directional margin at the marker; both Z values
lie within the outline’s base 0.3 and height 16. The next cataloged
terminal is about 131 m from the center and outside that outline. These are
structural observations and geometry calculations, not player-access evidence.
Do not redistribute either extracted sector or the extracted localization.
Experimental: check that Allen Street is accessible, the northeast route reaches the cabinet row, the row is recognizable without the quest pin, and the 110 m cleanup trigger covers the intended route.
Trace owners outward, then inward
Use two passes.
Pass 1: world registration
streamingworld.blockRefs
-> streamingblock.descriptors
-> descriptor.data
-> streamingsector
-> nodeData + nodes + nodeRefs
Record the descriptor index or distinguishing fields, sector depot path,
category, level, streaming box, and quest-prefab root. In the sector, locate
the placement by coordinate or full NodeRef, then record NodeIndex, node
type, transform, bounds, and QuestPrefabRefHash. Do not assume the three
sector arrays are parallel merely because one compact sample is.
Pass 2: referenced behavior
Follow only the references needed for the behavior:
trigger -> AreaShapeOutline buffer + notifier -> quest trigger condition
marker -> journal mappin reference -> quest mappin manager
device -> entity template + instanceData -> optional registry/persistence
For a quest-owned NodeRef, locate the phase prefab dependency and the exact
quest node or condition that consumes the local form. For a journal pin,
retain the narrow containing journal subtree and gameJournalPath, not the
entire cooked journal. For a device, separate the entity template, node-local
RedPackage, inplace resource, .devices, and .psrep questions.
Record the location
A useful location record contains:
| Field | Example of the required precision |
|---|---|
| Observation date and game build | Exact build/storefront used for the in-game visit |
| WolvenKit version | Exact version used to extract and serialize the focused view |
| Depot provenance | Exact paths and archive source for every retained claim |
| World identity | Full NodeRef plus local form only where its resolution context is known |
| Placement | Position, quaternion or yaw, scale, pivot, bounds, and coordinate source |
| Resource chain | Root, block descriptor, sector, node-data row, node, template, and optional inplace/registry owners |
| Focused excerpt | Only decisive fields, with array indices or stable identifiers |
| Evidence label | One of the four exact confidence labels, attached to the bounded claim |
| Runtime case | Starting save, route, expected result, observed result, logs, and fixture hashes |
Turn observation into a mod-owned fixture
Do not copy the extracted sector and delete most of it. Create the smallest mod-owned arrangement that expresses the researched relationship:
- Choose a unique namespace, prefab root, and child NodeRefs.
- Author a mod-owned sector with only the explained nodes.
- Author a mod-owned block and register it through ArchiveXL.
- Reference vanilla entity templates by depot path only when redistribution is unnecessary and the dependency is intentional.
- Preserve opaque cooked values from a known-good mod-owned seed or editor
output; do not invent meanings for
Uk*properties. - Round-trip, validate, pack, and inventory the archive.
- Test from a save made before any version of the fixture was installed.
Every supplied node and resource in a downloadable project must be explained. The project is executable reference material, not a magic template.
Separate the save from the archive
Facts, journal states, active objectives, tracker history, scenes, communities, and device persistent state can survive an archive rebuild. A location edit can appear to fix a static marker while the quest still follows an old checkpoint; the two observations diagnose different owners.
Retain at least:
- one pre-install clean save;
- one save before the area or device first streams;
- one save before the initial reach crossing;
- one mid-objective save after reach while still inside the outer leave trigger;
- one completed-state save;
- a written reset procedure that names every save-backed variable it changes.
Never describe “set the completion fact back to zero” as a clean-save reset. It does not clear journal, tracker, scene, community, or device state.
Location acceptance matrix
| Case | Evidence sought |
|---|---|
| Approach from each practical direction | Streaming, collision, access, marker height, and trigger boundary |
| Approach from a different elevation | Vertical trigger coverage and endpoint placement |
| Start outside | Negative control for state conditions and premature UI |
| Save/reload before entry | Registration and quest-state restoration |
| After reach, save/reload inside the outer leave area | Active leave-state behavior and loaded target resolution |
| Stream away and return before reach | Visible continuity and lookup errors while the objective is active; no precise unload claim without instrumentation |
| Fast travel nearby | Separate load/route case; never substitute it for an ordinary crossing |
| Complete and reload | One-shot guard, journal cleanup, and persistent world/device state |
| Remove/reinstall on an old save | Demonstrates save contamination; it is not the primary acceptance route |
Capture visible player-facing evidence and logs. A successful coordinate search, sector extraction, or static CR2W validation cannot prove navigation, interaction, or reload behavior.
Common failures
| Symptom | Likely research mistake |
|---|---|
| Correct coordinates, wrong sector | Multiple vertical/grid layers or a nearby composite prefab were conflated |
NodeIndex resolves the wrong node | Placement row order was treated as array identity |
| Copied reference works only in vanilla | Its prefab root, variant, global registry, or save identity was an unstated dependency |
| Marker is visible but route ends badly | Visual anchor and traversable navigation endpoint were treated as the same point |
| Device appears but custom behavior is absent | Template, node-local package, registry, and persistence owners were collapsed into one assumption |
| Trigger looks correct in properties but fires as a square | The visible points were trusted instead of AreaShapeOutline.buffer |
| Result changes after reinstall | Save-backed facts, journal, checkpoints, or device state contaminated the comparison |
| Research folder is too large to review | Whole vanilla resources were retained instead of focused excerpts and a provenance table |
Previous: Advanced devices, interactions, and persistent state. Next: Lab 3: Boundary Check.
Lab 3: Boundary Check
Lab 3 connects a registered questphase to mod-owned world streaming. The player follows a journal map pin to a checkpoint, enters a smaller trigger, then leaves a wider trigger to complete the quest.
| Record | Value |
|---|---|
| Structural validation date | 2026-08-09 |
| Runtime test date | Not yet recorded |
Implementation status: both supplied checkpoints are Structurally validated after all twelve checkpoint CR2W resources were cooked and serialized back with WolvenKit 8.19.0. Their native types, graph, NodeRef chain, sector placement records, outline buffers, journal paths, and block descriptors are exact. The test chapter checks mounting, streaming, marker/GPS presentation, trigger behavior, and save/reload behavior in game.
Follow Author Boundary Check in WolvenKit to expand the start checkpoint. Then use Test Boundary Check to bind the candidate, saves, visible observations, and logs.
What the lab demonstrates
The quest uses state-shaped trigger predicates so reloads on either side of a boundary are testable:
entry -> completed == 0?
True -> activate quest, phase, reach objective, and pin
-> wait until player IsInside the 25 m reach volume
-> retire pin, succeed reach, activate leave objective
-> wait until player IsOutside the 110 m leave volume
-> succeed leave and phase -> completed = 1 -> succeed quest -> end
False -> end
IsInside and IsOutside are not synonyms for Entered and Exited.
The former describe current state; the latter describe a transition event.
Vanilla resources contain all three shapes, but the behavior of this new
combination still needs its own runtime evidence.
Prerequisites and downloads
Complete Lab 1 and read:
- Streaming model;
- Quest prefabs and NodeRefs;
- Sector nodes and placement;
- Triggers and areas;
- Markers and navigation.
Use Cyberpunk 2077 2.31a for Windows (GOG), WolvenKit 8.19.0, ArchiveXL
1.27.0, RED4ext 1.30.0, and redscript 0.5.31.
- Download the start checkpoint. It has the entire mod-owned world scaffold and a two-node terminating phase.
- Download the completed checkpoint. It has the same scaffold and the exact 16-node quest graph.
Do not install the two checkpoints together. They register the same six depot paths. Use an untouched save created before either was first installed.
Resource ownership
| Resource | Depot path | Owns |
|---|---|---|
| Root questphase | mod\cqa\cqa003\phases\cqa003.questphase | Phase graph and root prefab declaration |
| Journal | mod\cqa\cqa003\journal\cqa003.journal | Quest, phase, two objectives, and the reach-objective map pin |
| Onscreen localization | mod\cqa\cqa003\localization\en-us\onscreens\cqa003.json | Title, two objectives, and pin caption |
| Streaming block | mod\cqa\cqa003\world\cqa003_boundary.streamingblock | Quest and AlwaysLoaded sector descriptors |
| Quest sector | mod\cqa\cqa003\world\cqa003_boundary.streamingsector | Reach and leave trigger node definitions and placements |
| AlwaysLoaded sector | mod\cqa\cqa003\world\cqa003_always_loaded.streamingsector | Static marker definition and placement |
| ArchiveXL registration | CQA_Lab03_BoundaryCheck.archive.xl | Root attachment, journal/localization merge, and streaming block registration |
ArchiveXL registers the block, not its two sectors separately. Descriptor
data paths make the sectors reachable from the block.
The root questphase declares one local prefab root:
#cqa003_pr_boundary
The world records use its mod-owned full form:
$/mod/cqa/cqa003/#cqa003_pr_boundary
Nested references are:
#cqa003_tr_reach
#cqa003_tr_leave
#cqa003_mp_checkpoint
The quest graph uses the local trigger refs because its root declares the
prefab. Sector nodeRefs and QuestPrefabRefHash records use the full child
refs. This single-root design avoids making a claim about dependency
inheritance across external child phases.
World layout
| Owner | Category / level | Node | Placement | Geometry or orientation |
|---|---|---|---|---|
| Quest sector | Quest / 255 | worldTriggerAreaNode reach | (-1000.02, 1497.2208, 2.3) | 16-point, 25 m radius, 12 m high |
| Quest sector | Quest / 255 | worldTriggerAreaNode leave | (-1000.02, 1497.2208, 0.3) | 20-point, 110 m radius, 16 m high |
| AlwaysLoaded sector | AlwaysLoaded / 1 | worldStaticMarkerNode | (-1000.02, 1497.2208, 8.3) | yaw 88.6° |
These are inherited test-site values, not a promise that the location is reachable or the volumes are appropriate on every game build. The acceptance matrix—not structural inspection—governs runtime claims about the marker, both outlines, their vertical coverage, and the finite padded Quest descriptor bounds.
The site is the outdoor recycling-station cabinet row in Watson/Kabuki. A focused vanilla sector, TweakDB, and localization chain identifies the Allen Street terminal and linked marker about 90 m southwest of the lab center; Test Boundary Check explains how to verify that ordinary route before installing the candidate. Those identities and coordinates are Observed in vanilla. The retained matrix governs runtime claims about practical access, the route, and the new 110 m boundary.
Each Quest placement’s NodeIndex selects the corresponding local entry in
nodes; the marker placement selects the only marker node. This is true for
the compact mod-owned sectors. It is not a universal parallel-array rule for
all vanilla sectors.
Authoritative trigger geometry
Each trigger owns a questTriggerNotifier_Quest and an
AreaShapeOutline. The outline’s encoded buffer contains point count,
four-float points, and height. Repository validation decodes that buffer and
requires it to match the intended 16/20 points and 12/16 heights.
Do not change only the visible points or height. Retained vanilla exports
show that the displayed JSON points can be defaults while the buffer carries
different authoritative geometry. If the outline changes, regenerate or
reserialize all three representations through a tool path that updates the
buffer, then inspect the cooked result.
MaxStreamingDistance is named and compared. UkFloat1 is retained as an
opaque field; this book does not rename it to a streaming distance without
evidence.
Journal and marker contract
quests
└── minor_quest
└── cqa003
└── cqa003_01
├── cqa003_01_obj_reach
│ └── cqa003_01_qmp_checkpoint
└── cqa003_01_obj_leave
| Journal entry | Localization key | English text |
|---|---|---|
| Quest title | cqa_cqa003_title | Boundary Check |
| Reach objective | cqa_cqa003_objective_reach | Reach the marked checkpoint. |
| Leave objective | cqa_cqa003_objective_leave | Leave the checkpoint area. |
| Map-pin caption | cqa_cqa003_mappin_checkpoint | Boundary Check checkpoint |
The journal map pin references #cqa003_mp_checkpoint and enables GPS. The
static marker places an anchor; the journal entry owns player-facing mappin
data; quest Mappin Manager nodes activate and deactivate that journal state.
None of those records substitutes for the other two.
Both Lab 3 Mappin Manager nodes set disablePreviousMappins: 0. This isolated
lab has no deliberate previous route to replace; activation and inactivation
come from the nodes’ Active and Inactive sockets.
Exact questphase
The figure is generated from the completed checkpoint. Validation resolves socket handles and requires exactly 16 nodes and 16 edges.
| ID | RED node type | Decisive payload | Purpose |
|---|---|---|---|
0 | questInputNodeDefinition | interface In1 | Enter registered root |
1 | questOutputNodeDefinition | terminating Out1 | End success and bypass routes |
10 | questConditionNodeDefinition | cqa003_completed Equal 0 | Bypass a completed save |
11 | questJournalNodeDefinition | quest path | Activate Boundary Check |
12 | questJournalNodeDefinition | phase path | Activate phase |
13 | questJournalNodeDefinition | reach objective | Activate reach objective |
14 | questMappinManagerNodeDefinition | map-pin path, disable previous 0 | Activate/track checkpoint pin |
15 | questPauseConditionNodeDefinition | player IsInside #cqa003_tr_reach | Wait for current inside state |
16 | questMappinManagerNodeDefinition | same map-pin path | Inactivate checkpoint pin |
17 | questJournalNodeDefinition | reach objective | Succeed reach objective |
18 | questJournalNodeDefinition | leave objective | Activate leave objective |
19 | questPauseConditionNodeDefinition | player IsOutside #cqa003_tr_leave | Wait for current outside state |
20 | questJournalNodeDefinition | leave objective | Succeed leave objective |
21 | questJournalNodeDefinition | phase path | Succeed phase |
22 | questFactsDBManagerNodeDefinition | set cqa003_completed = 1 exactly | Persist one-shot completion |
23 | questJournalNodeDefinition | quest path | Succeed Boundary Check |
Every node has one explained responsibility. The graph does not contain a hidden hub, child phase, scene, community, device, or script dependency.
Exact edge contract
| Source | Destination | Meaning |
|---|---|---|
0.Out | 10.In | Enter guard |
10.False | 1.In | Completed-save bypass |
10.True | 11.Active | First-run activation |
11.Out | 12.Active | Activate phase |
12.Out | 13.Active | Activate reach objective |
13.Out | 14.Active | Activate pin |
14.Out | 15.In | Arm reach condition |
15.Out | 16.Inactive | Retire pin after reach |
16.Out | 17.Succeeded | Succeed reach objective |
17.Out | 18.Active | Activate leave objective |
18.Out | 19.In | Arm leave condition |
19.Out | 20.Succeeded | Succeed leave objective |
20.Out | 21.Succeeded | Succeed phase |
21.Out | 22.In | Write completion fact |
22.Out | 23.Succeeded | Succeed quest |
23.Out | 1.In | Terminate successful route |
The only persistent fact is cqa003_completed. Quest, journal, active-node,
world, and marker state can also be save-backed even though they are not
FactsDB names.
Common failure modes
| Symptom | Check first | Boundary |
|---|---|---|
| Quest never appears | Installed archive plus loose .archive.xl, root phase/journal/localization paths, ArchiveXL and RED4ext logs | Structural validation does not establish runtime activation |
| Objective appears but pin does not | Journal map-pin path, marker NodeRef, AlwaysLoaded descriptor, and Mappin Manager state socket | Do not infer GPS or sector behavior from the objective alone |
| Pin appears at the wrong height | Marker placement Z, yaw, terrain accessibility, and clean starting save | Judge inherited coordinates from the exact candidate’s retained runtime evidence |
| Entering does nothing | Quest-sector descriptor root, full child ref, local condition ref, player activator flag, trigger notifier, and decoded reach buffer | JSON points alone are not geometry evidence |
| Leave completes immediately | Confirm the starting save is inside the outer volume when leave activates and the condition is IsOutside | A dirty save or bad vertical coverage changes the initial state |
| Area works until streaming away | Finite Quest bounds, sector logs, return route, and NodeRef resolution | One successful nearby crossing does not prove unload/reload behavior |
| Quest reactivates after reload | completed write order, guard comparison, exact installed build, and untouched/completed save identity | Console resets are diagnostic, not clean acceptance |
Lab sequence: All labs · Next: Author Boundary Check in WolvenKit.
Author Boundary Check in WolvenKit
Build Boundary Check in WolvenKit using the start checkpoint. You will add the quest graph to the supplied streaming, journal, and localization resources.
| Record | Value |
|---|---|
| Structural validation date | 2026-08-09 |
| Runtime test date | Not yet recorded |
Use Cyberpunk 2077 2.31a for Windows (GOG), WolvenKit 8.19.0, ArchiveXL
1.27.0, RED4ext 1.30.0, and redscript 0.5.31. Other versions may expose
different editor wrappers or runtime behavior.
1. Open and isolate the checkpoint
- Download and extract the start ZIP.
- Open
CQA_Lab03_BoundaryCheck_Start.cpmodprojwith File > Open Project. - Make an untouched backup of the extracted directory.
- Confirm that
source\archiveandsource\raweach contain the same six depot resources listed in the lab overview. - Confirm
CQA_Lab03_BoundaryCheck_Start.archive.xlis undersource\resources. - Do not install the start and completed checkpoints together.
The phase should contain only [0] Input connected to [1] Output. Confirm
Input socketName: In1, Output socketName: Out1, and Output
type: Terminating.
2. Audit registration before graph work
Open the ArchiveXL file and retain all four sections:
quest:
phases:
- path: mod\cqa\cqa003\phases\cqa003.questphase
parent: base\quest\cyberpunk2077.quest
journal:
- mod\cqa\cqa003\journal\cqa003.journal
localization:
onscreens:
en-us:
- mod\cqa\cqa003\localization\en-us\onscreens\cqa003.json
streaming:
blocks:
- mod\cqa\cqa003\world\cqa003_boundary.streamingblock
The block owns the two sector dependencies. Do not add the sectors to a
guessed top-level registration list. base\quest\cyberpunk2077.quest is a
vanilla reference; do not include an extracted copy.
3. Audit the journal, strings, and map pin
Open cqa003.journal and confirm this typed tree:
gameJournalRootFolderEntry
└── quests gameJournalPrimaryFolderEntry
└── minor_quest gameJournalFolderEntry
└── cqa003 gameJournalQuest
└── cqa003_01 gameJournalQuestPhase
├── cqa003_01_obj_reach
│ └── cqa003_01_qmp_checkpoint
└── cqa003_01_obj_leave
The map-pin entry is gameJournalQuestMapPin. Confirm:
| Property | Value |
|---|---|
enableGPS | 1 |
reference.reference | #cqa003_mp_checkpoint |
offset | (0, 0, 0.5) |
mappinData.active | 0 |
mappinData.mappinType | Mappins.QuestStaticMappinDefinition |
mappinData.variant | DefaultQuestVariant |
mappinData.visibleThroughWalls | 1 |
| localized/debug caption | cqa_cqa003_mappin_checkpoint |
Open the onscreen localization resource and confirm four entries with primary
key 0, empty male variants, and these exact joins:
secondaryKey | femaleVariant |
|---|---|
cqa_cqa003_title | Boundary Check |
cqa_cqa003_objective_reach | Reach the marked checkpoint. |
cqa_cqa003_objective_leave | Leave the checkpoint area. |
cqa_cqa003_mappin_checkpoint | Boundary Check checkpoint |
Save, close, and reopen both resources. Do not edit the raw JSON review copies.
4. Audit the root prefab declaration
Open cqa003.questphase in the resource editor. Its root
questQuestPhaseResource has:
phasePrefabs[0].prefabNodeRef = #cqa003_pr_boundary
inplacePhases = []
Keep exactly one declaration. phasePrefabs is a root-resource collection;
phaseInstancePrefabs belongs to a Phase node. This lab has no child phase
node and needs no duplicate declaration.
The full world root is
$/mod/cqa/cqa003/#cqa003_pr_boundary. Local graph conditions refer to child
refs under that declared root.
5. Audit the streaming block
Open cqa003_boundary.streamingblock. It has two
worldStreamingSectorDescriptor records:
| Property | Quest descriptor | AlwaysLoaded descriptor |
|---|---|---|
category | Quest | AlwaysLoaded |
level | 0 | 1 |
data | ...\cqa003_boundary.streamingsector | ...\cqa003_always_loaded.streamingsector |
questPrefabNodeRef | $/mod/cqa/cqa003/#cqa003_pr_boundary | zero/unset |
numNodeRanges | 1 | 1 |
variants | empty | empty |
The Quest descriptor’s finite box is:
Min (-1300.02, 1197.2208, -291.7)
Max ( -700.02, 1797.2208, 308.3)
The AlwaysLoaded descriptor uses the supplied broad -99999..99999 box.
Descriptor and sector levels are separate properties: the Quest descriptor is
level 0, while the Quest sector root below is level 255. Do not normalize
them to the same number.
The chosen bounds are Experimental. Keep them unchanged for canonical comparison; tune bounds only as a separately hashed candidate.
6. Audit the Quest sector and placements
Open cqa003_boundary.streamingsector. Confirm root category Quest, level
255, two nodes, two nodeData.Data records, and two full nodeRefs.
NodeIndex | Local node | Full QuestPrefabRefHash | Position |
|---|---|---|---|
0 | reach worldTriggerAreaNode | $/mod/cqa/cqa003/#cqa003_pr_boundary/#cqa003_tr_reach | (-1000.02, 1497.2208, 2.3) |
1 | leave worldTriggerAreaNode | $/mod/cqa/cqa003/#cqa003_pr_boundary/#cqa003_tr_leave | (-1000.02, 1497.2208, 0.3) |
Both placements use yaw 88.6°, unit scale, zero/unset cooked prefab data,
and these distance-shaped fields:
| Child | MaxStreamingDistance | UkFloat1 |
|---|---|---|
| reach | 320 | 280 |
| leave | 360 | 320 |
Treat UkFloat1 as opaque. Similar numerical ordering does not establish its
meaning.
For these compact supplied sectors, NodeIndex selects a local node. Do not
turn that into a universal rule: some vanilla sectors register refs or use
indices outside a local nodes array.
7. Audit each trigger and its authoritative outline
Each node is a worldTriggerAreaNode with one
questTriggerNotifier_Quest, includeChannels: TC_Default, and
isEnabled: 1.
| Child ref | Outline points | Radius | Height |
|---|---|---|---|
#cqa003_tr_reach | 16 | 25 | 12 |
#cqa003_tr_leave | 20 | 110 | 16 |
Open the AreaShapeOutline and inspect the serialized resource, not only the
canvas preview. Its binary buffer begins with a little-endian point count,
then stores each point as four floats (x, y, z, 1.0), then the height float.
The supplied resources were verified by decoding those bytes.
Do not manually edit only points or height. If you intentionally change an
outline, use a WolvenKit operation that rebuilds the buffer, save and reopen,
then serialize the cooked result for an independent buffer check. The
canonical walkthrough keeps the supplied geometry unchanged.
8. Audit the marker sector
Open cqa003_always_loaded.streamingsector. Confirm category
AlwaysLoaded, level 1, and one worldStaticMarkerNode selected by
NodeIndex: 0.
| Property | Value |
|---|---|
| Full placement ref | $/mod/cqa/cqa003/#cqa003_pr_boundary/#cqa003_mp_checkpoint |
| Position | (-1000.02, 1497.2208, 8.3) |
| Yaw | 88.6° |
MaxStreamingDistance | 360 |
UkFloat1 | 320 (opaque) |
The marker is an anchor. It is not the journal map pin, and it does not change journal state. Keep the three-owner chain intact: sector marker, journal map pin, graph Mappin Manager.
9. Add and number graph nodes
In the questphase graph, add these nodes in order. Input 0 and Output 1
already exist.
| Target ID | Add Node label | RED type |
|---|---|---|
10 | Condition | questConditionNodeDefinition |
11 | Journal | questJournalNodeDefinition |
12 | Journal | questJournalNodeDefinition |
13 | Journal | questJournalNodeDefinition |
14 | MappinManager | questMappinManagerNodeDefinition |
15 | PauseCondition | questPauseConditionNodeDefinition |
16 | MappinManager | questMappinManagerNodeDefinition |
17 | Journal | questJournalNodeDefinition |
18 | Journal | questJournalNodeDefinition |
19 | PauseCondition | questPauseConditionNodeDefinition |
20 | Journal | questJournalNodeDefinition |
21 | Journal | questJournalNodeDefinition |
22 | FactsDBManager | questFactsDBManagerNodeDefinition |
23 | Journal | questJournalNodeDefinition |
WolvenKit normally assigns lower automatic IDs. Renumber safely in two passes:
move the new nodes to temporary IDs 100–113, then to target IDs 10–23.
Never overwrite an ID still owned by another node. Save, close, reopen, and
confirm the exact inventory before wiring.
10. Configure the completion guard and fact writer
At ID 10, add a questFactsDBCondition, then
questVarComparison_ConditionType:
| Property | Value |
|---|---|
factName | cqa003_completed |
comparisonType | Equal |
value | 0 |
At ID 22, choose questSetVar_NodeType and set:
| Property | Value |
|---|---|
factName | cqa003_completed |
setExactValue | 1 |
value | 1 |
Exact assignment makes the one-shot result independent of an old numeric value. Do not use a console write in the canonical runtime run.
11. Configure journal operations
For Journal IDs, add questJournalQuestEntry_NodeType and a typed
gameJournalPath. Keep these common values:
| Property | Value |
|---|---|
type.optional | 0 |
type.sendNotification | 1 |
type.trackQuest | 1 |
type.version | Initial |
type.path.editorPath | empty |
type.path.fileEntryIndex | 2 |
| IDs | className | realPath |
|---|---|---|
11, 23 | gameJournalQuest | quests/minor_quest/cqa003 |
12, 21 | gameJournalQuestPhase | quests/minor_quest/cqa003/cqa003_01 |
13, 17 | gameJournalQuestObjective | quests/minor_quest/cqa003/cqa003_01/cqa003_01_obj_reach |
18, 20 | gameJournalQuestObjective | quests/minor_quest/cqa003/cqa003_01/cqa003_01_obj_leave |
Incoming sockets choose state. IDs 11, 12, 13, and 18 receive
Active; IDs 17, 20, 21, and 23 receive Succeeded.
12. Configure map-pin operations
Both Mappin Manager nodes use this typed path:
| Property | Value |
|---|---|
path.className | gameJournalQuestMapPin |
path.realPath | quests/minor_quest/cqa003/cqa003_01/cqa003_01_obj_reach/cqa003_01_qmp_checkpoint |
path.fileEntryIndex | 2 |
path.editorPath | empty |
Set both ID 14 and ID 16 to disablePreviousMappins: 0. ID 14 receives
the Active socket and ID 16 receives the Inactive socket; both paths point
to the same journal entry. This isolated lab has no deliberate prior route to
replace. Use 1 only for a separately tested transition that intentionally
clears previous routing.
13. Configure trigger Pause Conditions
At each Pause Condition, add a questTriggerCondition at condition:
| ID | condition.type | triggerAreaRef |
|---|---|---|
15 | IsInside | #cqa003_tr_reach |
19 | IsOutside | #cqa003_tr_leave |
Set isPlayerActivator: 1. Leave activatorRef as the ordinary empty
gameEntityReference; do not substitute a guessed entity path. These are
state-shaped waiting nodes. Do not change them to Entered/Exited while
comparing with the canonical checkpoint.
14. Wire the exact graph
Use Show Unused Sockets, then create these 16 ordinary edges:
| Source | Destination |
|---|---|
0.Out | 10.In |
10.False | 1.In |
10.True | 11.Active |
11.Out | 12.Active |
12.Out | 13.Active |
13.Out | 14.Active |
14.Out | 15.In |
15.Out | 16.Inactive |
16.Out | 17.Succeeded |
17.Out | 18.Active |
18.Out | 19.In |
19.Out | 20.Succeeded |
20.Out | 21.Succeeded |
21.Out | 22.In |
22.Out | 23.Succeeded |
23.Out | 1.In |
Leave every CutDestination unwired. Save, close, reopen, then compare IDs,
types, payloads, sockets, and edges with the
exact generated graph. Canvas position and
handle IDs are not behavior.
15. Validate, build, and install
With every tab saved:
- run Project > Scan for broken file references;
- run Project > Scan for broken files;
- run Project > Run File Validation on the entire project;
- resolve all project-owned errors;
- close Cyberpunk 2077;
- choose Build > Install and wait for packing and installation;
- inspect WolvenKit’s Log panel and both installed files.
Use the normal ArchiveXL project install, not Install as REDmod. A packed
archive without its loose .archive.xl cannot attach the phase, merge the
journal/localization, or register the block.
The authored start project installs _Start-named files. To test the supplied
completed project, open CQA_Lab03_BoundaryCheck.cpmodproj and remove the
start project’s installed files before installing it.
Common authoring failures
| Symptom | Check |
|---|---|
| Graph sockets move or disappear after reopen | Renumber through 100–113, save, close, reopen, then wire only after final IDs are unique |
| Trigger wait never releases | Confirm Pause Condition, questTriggerCondition, player activator, exact local ref, matching full sector child ref, notifier, and decoded outline |
| Leave completes as soon as it activates | Confirm outer volume and Z coverage, IsOutside, and that the test save is actually inside the outer volume at reach success |
| Pin cannot resolve | Keep marker full ref, journal local ref, gameJournalQuestMapPin path, and active/inactive Mappin Manager operations aligned |
| Block exists but sectors do not appear | Keep both descriptor data paths, Quest root ref, category/level pairs, finite Quest bounds, and ArchiveXL block registration |
| Editing points changes nothing | The AreaShapeOutline.buffer is authoritative; update and revalidate the complete outline serialization |
| Build succeeds but nothing starts | Confirm installed archive plus loose registration file and inspect logs for all four registered top-level resources |
| Canonical hashes no longer match | Use the supplied completed checkpoint; a manually expanded _Start project is not byte-identical evidence |
Lab sequence: All labs · Previous: Lab 3: Boundary Check · Next: Test Boundary Check.
Test Boundary Check
Lab 3 crosses quest, journal, localization, streaming, marker, trigger, and save systems. A screenshot of the final journal state cannot isolate those owners. This protocol binds the exact installed candidate, save history, visible transitions, and fresh logs across eight executions.
| Record | Value |
|---|---|
| Structural validation date | 2026-08-09 |
| Runtime test date | Not yet recorded |
Use the unmodified completed checkpoint for these tests. Record what you observe for every run; a successful ordinary walk does not establish reload, stream-return, reinstall, or clean-replay behavior.
Required environment
| Component | Exact version |
|---|---|
| Cyberpunk 2077 for Windows (GOG) | 2.31a |
| WolvenKit | 8.19.0 |
| ArchiveXL | 1.27.0 |
| RED4ext | 1.30.0 |
| redscript | 0.5.31 |
Record observed versions on every run. Values printed in the template are requirements, not proof of the machine that executed it.
Establish the site and ordinary control route
Do this before installing any Lab 3 candidate. The quest pin and GPS route are under test, so they cannot be the only way you know how to reach the site.
Observed in vanilla: a focused extract from
base\worlds\03_night_city\_compiled\default\exterior_-17_22_0_0.streamingsector
places a fast-travel terminal entity at
(-1064.30457, 1436.18298, 4.95000076). Node index 1766 uses record
FastTravelPoints.wat_kab_dataterm_12, entity template
base\gameplay\devices\fast_travel\data_term_1.ent, and this full NodeRef:
$/03_night_city/c_watson/kabuki/kabuki_data_terminals_prefabW7DFTCQ/#wat_kab_dataterm_12/{kab_data_term_}12_prefabBARKVWA
The installed TweakDB maps that record to LocKey#79217; English
base\localization\en-us\onscreens\onscreens.json maps key 79217 to
Allen Street. Its linked destination marker is node 5757 in
base\worlds\03_night_city\_compiled\default\always_loaded_1.streamingsector:
$/03_night_city/c_watson/kabuki/kabuki_data_terminals_prefabW7DFTCQ/#wat_kab_dataterm_12/#wat_kab_dataterm_12_tp
(-1065.76489, 1435.83472, 4.94000006)
Extract those resources from your own game if you need to reproduce the
lookup; do not redistribute them. The Lab 3 center is the outdoor
recycling-station cabinet row in Watson/Kabuki at
(-1000.02, 1497.2208, 8.3), about 90 m northeast of the linked marker. The
20-sided 110 m outline calculation leaves about 18.8 m of directional margin
at that marker; the terminal and marker Z values lie within the outline’s base
0.3 and height 16. The next cataloged terminal is about 131 m away and
outside the outline.
Those coordinates and calculations do not prove player access. Treat the following as Experimental until performed on the pinned game build:
- with no Lab 3 candidate installed, visit and unlock Allen Street;
- confirm the terminal opens normally and that at least one distant destination is already unlocked;
- from the terminal, walk roughly northeast for about 89 m to the outdoor recycling-station cabinet row;
- recognize the cabinet row independently of any quest marker and confirm an ordinary public approach, collision, and practical elevation;
- walk back to Allen Street and make the untouched outside save there.
If the terminal or cabinet route is inaccessible, record that result and do not silently replace the route with a console teleport. Choose an accessible test route before continuing.
Prepare test saves
Before installing any Lab 3 checkpoint:
- choose the Allen Street manual save made above, which has never loaded a
build registering
cqa003; - confirm the player is outside the 25-metre reach volume and can reach the cabinet row by the verified ordinary route;
- copy the entire save-slot directory into your test folder;
- record its label and slot-directory name;
- confirm that this save predates the first Lab 3 install;
- retain one unchanged active-before-reach derivation for both Run 2 and Run 4; restore byte-for-byte working copies from it rather than making a second pre-reach state;
- reserve separate derived copies for between-boundaries and completed reloads;
- reserve a separate between-boundaries derivation for fast travel.
A console reset of cqa003_completed does not create a clean save. Removing
the mod after a prior load does not remove serialized journal, graph, marker,
or world state.
Build and bind the canonical candidate
Use the unmodified completed checkpoint:
candidate id: canonical
project: CQA_Lab03_BoundaryCheck.cpmodproj
-
Close the game.
-
Build and install through WolvenKit.
-
Confirm only this candidate’s pair is installed:
archive\pc\mod\CQA_Lab03_BoundaryCheck.archive archive\pc\mod\CQA_Lab03_BoundaryCheck.archive.xl -
Confirm the archive contains all six depot resources, including the two sectors reached through the registered block.
-
Preserve the installed files until every run needing this candidate is complete.
Run 1: outside negative control and ordinary walk
Restore the untouched Allen Street save and record the execution as
clean-walk. Follow the independently learned route roughly 89 m northeast
from the terminal to the outdoor cabinet row. Do not count the quest GPS route
as proof that the ordinary route is usable.
Before entering
Observe and retain:
- Boundary Check, its phase, and the reach objective activate once;
- the checkpoint pin appears at the intended location;
- the minimap/world-map presentation and any GPS route are usable;
- waiting outside the reach boundary does not advance the objective;
- approaching at different elevations does not trip the volume unexpectedly.
Visible pin placement is evidence about presentation. It does not identify which sector loaded. Record displaced, inaccessible, missing, flickering, or route-less behavior exactly rather than smoothing it into a pass.
Cross the reach boundary
Walk into the inner volume. Confirm the pin retires, the reach objective succeeds once, and the leave objective activates once. Stay inside the outer volume long enough to show that leave does not complete merely because the new objective became active.
Cross the leave boundary on foot
Walk southwest along the learned route, pass the Allen Street terminal, and continue at least another 40 m in the same general direction. The structural calculation puts the linked marker about 18.8 m inside the outline along that ray, so the terminal itself is not the expected foot crossing. Record the crossing position/Z, confirm the leave objective, phase, and quest succeed once, and wait long enough to detect a delayed duplicate pin or journal notification.
This run supplies outside-negative-control-and-foot-route and
marker-and-navigation. The exact graph and resources map those visible
events to authored nodes; UI alone does not expose a trigger buffer or sector.
Run 2: reload before reach
Restore the original untouched save, activate the quest while remaining
outside the reach volume, and make a derived save. Hash it and execute
pre-reach-reload:
- reload without changing the installation;
- confirm the reach objective and pin return once;
- enter the reach volume;
- confirm one pin retirement and one reach success;
- finish on foot and retain the result.
Preserve this source slot unchanged. Run 4 must use a working copy of this same active-before-reach save.
The required result is coherent single progression. Do not infer how the engine rearmed the state predicate solely from the journal UI.
Run 3: reload between boundaries
Restore the clean source, reach the checkpoint, and save after the reach
objective succeeds while still inside the 110-metre leave volume. Hash that
save and execute between-boundaries-reload:
- reload in place;
- confirm leave stays active instead of completing on load;
- remain inside briefly as a negative control;
- follow the Allen Street route past the terminal and cross the outer boundary on foot;
- confirm single completion.
If the volume releases immediately, record the player’s exact position/Z, save state, and logs. Do not silently move the boundary or change predicate mid-candidate.
Run 4: stream away and return
Load a working copy of Run 2’s active-before-reach save for stream-away-return.
- remain outside the reach volume and go to the recognizable Allen Street terminal by ordinary movement; do not open or use it;
- follow connected public streets southwest from Allen Street by ordinary
walking or driving until the map shows at least 500 m of displacement from
the terminal, then continue another two blocks so the route is well beyond
the site-centered finite box
(-1300.02..-700.02, 1197.2208..1797.2208); - record the farthest map location or player position and whether the active objective and pin remain coherent;
- reverse the same street route, pass the Allen Street terminal again, then follow the learned roughly 89 m northeast route to the cabinet row;
- walk the reach and leave sequence and retain any streaming or NodeRef log lines.
Do not use fast travel in this run. If road geometry prevents the stated route, record the deviation and the farthest position rather than assuming the test box was left.
This case probes the finite Quest descriptor bounds and return path. It does not prove a precise unload moment unless independent instrumentation records one. Describe player-visible continuity and log results without inventing an invisible streaming event.
Run 5: characterize fast travel separately
Use the separately retained between-boundaries save for fast-travel-exit.
- after loading, confirm the leave objective is active at the cabinet row;
- walk the learned ordinary route southwest to the Allen Street terminal;
- before opening it, record the player position and Z and confirm the leave objective has not completed; the structural calculation predicts about 18.8 m of marker margin, but this observation is the runtime check;
- open Allen Street and choose any already-unlocked destination that you independently confirmed is outside the 110 m leave outline;
- after arrival, record whether
IsOutsidecompletes, remains waiting, or behaves another way, together with the arrival position/Z and logs.
If leave completes before the terminal opens, mark the required
fast-travel-characterization case failed and retain the position evidence;
do not continue that execution as though fast travel caused the exit.
This is a required characterization, not a predetermined claim that fast
travel must simulate Exited. Keep it separate from Run 1’s ordinary foot
crossing. Allen Street accessibility and the new 110 m behavior remain
Experimental until this run is retained.
Run 6: completed-save reload
From an ordinary clean-walk completion:
- preserve the completed save;
- reload it without changing installed files as
completed-reload; - confirm no quest/objective reactivation and no checkpoint pin;
- wait for delayed visible updates;
- retain fresh logs.
The completed guard’s bypass is node 10.False. Check that the completed
save does not reactivate the quest.
Run 7: identical reinstall
Close the game, remove the installed pair, and reinstall the preserved copies of those same files.
Load Run 6’s completed save as completed-reinstall. Confirm the quest stays
complete, the pin stays inactive, and no duplicate activation occurs.
Run 8: clean replay
Restore the original untouched save-slot directory and leave the same completed build installed.
Execute clean-replay, walking the ordinary route again. The same visible
activation, reach, leave, and completion sequence must occur once. Retain a
new log set rather than reusing Run 1’s files.
Retain four logs per execution
After each of the eight executions, close the game and retain fresh copies of:
red4ext\plugins\ArchiveXL\ArchiveXL.log
red4ext\logs\red4ext.log
red4ext\logs\game.log
r6\logs\redscript_rCURRENT.log
Hash every file under its run. Search for the registration filename, cqa003,
all registered depot paths, the streaming block and sectors, NodeRefs,
journal/localization lookups, and condition errors. Clean logs mean those
systems did not report an error; they do not replace in-game observations.
Common test failures
| Symptom | Check |
|---|---|
| Quest is already active on the first run | Replace the whole slot with one captured before any cqa003 install; fact resets do not clean journal/world state |
| Pin appears but route cannot reach it | Record the map/minimap/GPS result, site accessibility, marker Z, and exact game build; do not count visible pin alone as navigation success |
| Allen Street is locked or the cabinet row cannot be reached ordinarily | Stop before acceptance, retain the observed route/access result, and establish a separately reviewed route; do not substitute a diagnostic teleport |
| Trigger result is ambiguous | Record position, Z, ordinary movement versus fast travel, visible journal state, installed build, and logs; keep state and transition claims distinct |
| Leave completes while walking back to Allen Street | Retain the exact player position/Z and installed build as a failed geometry result; do not continue as though the terminal were inside the outline |
| Leave completes on reload | Verify the derived save is actually inside the outer volume and retain its position evidence; do not move geometry inside the canonical candidate |
| Stream-away result is inferred | Report only visible continuity and logs unless instrumentation independently establishes unload/reload |
| Reinstall is not identical | Compare both installed filenames; a new build is a new candidate even when source looks unchanged |
| One screenshot is used for every case | Retain separate run identity, saves, timestamps, observations, and four-log bundles |
| UI is treated as proof of sector ownership | Use exact structural evidence for ownership and runtime evidence only for what the player/logs expose |
Lab sequence: All labs · Previous: Author Boundary Check in WolvenKit · Next lab: Lab 4: Handoff Point.
Communities and characters
A community is the native world-side system that turns a character record and one or more AI spots into actors that quest logic can activate, wait for, hand to a scene, and eventually deactivate. No single resource owns that complete lifecycle:
always-loaded community registry
+ streamed compiled community area
+ streamed AI spot and workspot
+ questphase activation and readiness gate
+ scene actor acquisition
+ delayed questphase cleanup
Treat those as cooperating owners. A valid TweakDBID does not place an actor,
an AI spot does not activate its community, and a scene actor definition does
not spawn the character it intends to acquire.
Advanced characters add two more chains without replacing the community:
Character record -> .ent exposed mapping -> .app internal definition
spawned community actor -> Gameplay AI tier -> temporary role/workspot
-> observed result -> scene/phase/cleanup handoff
The first controls gameplay defaults and renderable presentation. The second temporarily changes what one spawned actor is doing. Neither should borrow a story-unique character or mutate a shared vanilla resource.
The beginner-safe lifecycle is:
Activate community
-> wait for CharacterSpawned
-> enter a broad setup area
-> start the scene
-> acquire the community entry
-> consume the named scene outcome
-> advance outcome-specific journal or branch state
-> wait until V leaves a cleanup area
-> Deactivate community
-> complete the owning phase and its terminal one-shot state
The order is part of the design. Starting the scene immediately after
Activate creates a race between actor materialization and scene acquisition.
Deactivating on the scene’s last spoken line can remove an actor while the
scene, AI, or workspot still owns it.
Native ownership map
| Owner | Responsibility |
|---|---|
worldCommunityRegistryNode | Makes community template data and initial entry state available independently of the local streamed area. |
worldCompiledCommunityAreaNode_Streamable | Places the community source in the streamed world and mirrors which entry/phase/time period uses which AI-spot identities. |
worldAISpotNode | Places an AIActionSpot and names the workspot resource an actor can use there. |
communitySpawnEntry | Associates one community entry name with a character TweakDBID and one or more spawn phases. |
communitySpawnPhase | Selects the phase name, appearance set, and time-period behavior for that entry. |
Character.* TweakDB record | Selects the entity template and supplies inherited reaction, faction, action-map, archetype, equipment, voice, and other gameplay defaults. |
entEntityTemplate | Owns the puppet scaffold, root components, default exposed appearance, and exposed-to-internal appearance mappings. |
appearanceAppearanceResource | Owns internal appearance definitions, component graphs, frame type, and render/runtime dependencies. |
questSpawnManagerNodeDefinition | Sends Activate or Deactivate to a community reference; it does not prove the actor already exists or is gone. |
questCharacterSpawned_ConditionType | Waits for the configured spawned-character comparison to become true. |
questPuppetAIManagerNodeDefinition | Requests an AI/story tier for selected spawned actors. |
questMiscAICommandNode | Carries a typed AI command such as follower/patrol role assignment or role clear. |
questUseWorkspotNodeDefinition | Issues a direct workspot command to an actor; its output does not replace state observation. |
scnActorDef with acquisitionPlan: community | Acquires an already available community entry for scene performance. |
| Save | Retains quest, scene, community, and actor-related state that can invalidate a dirty retest. |
This builds on the resource model, identifier domains, world streaming model, and persistent state.
Three world identity domains
The retained compact community shape places three numeric identity domains beside one another:
- the community/source identity shared by the compiled area’s
sourceObjectIdand its registry item’scommunityId; - the separate world-node identity of the registry node itself;
- one separate world-global identity for every AI spot.
They must not be collapsed because all three can appear as 64-bit values. The
AI spot’s full NodeRef, the community entry’s CName, and its character
record’s TweakDBID are additional typed identities. A debug name is none of
them.
Registries and compiled areas explains the exact joins and the collision that motivated this rule.
Reading route
Read these chapters in order:
- Registries and compiled areas — where template data lives, how a streamed source meets its registry item, and why registry, source, and spot identities stay separate.
- Entries, phases, and AI spots — how an entry selects a character record, appearance, time period, and placed workspot.
- Activation, readiness, and acquisition
— why
Activate,CharacterSpawned, broad setup, and scene acquisition are four different lifecycle steps. - Cleanup and character safety — delayed deactivation, save-aware tests, unique-character hazards, and the bounded generic-character recommendation.
- Character records, entities, and appearances — the native record-to-entity-to-appearance chain, typed identities, mod-owned authoring boundary, and render/lifecycle acceptance matrix.
- AI roles, behavior, and workspots — record behavior, Gameplay tier, follower/patrol roles, direct workspot commands, state observation, and ownership transfer.
The later scene chapters own screenplay, actor/performer, entry-point, and named-exit details. This section explains only the community side of their join.
Vanilla references
Extract the following resources from your own game with WolvenKit. Do not copy or redistribute the extracted CR2W files.
| Depot path | Question it answers |
|---|---|
base\quest\minor_quests\mq003\mq003_orbitals.questphase | How a vanilla parent activates a whole community, waits for spawned readiness, and invokes the activity phase. |
base\quest\minor_quests\mq003\phases\mq003_homeless.questphase | How named community entries change phase and how the activity eventually deactivates them. |
base\quest\minor_quests\mq003\scenes\mq003_01_homeless.scene | How scene actors use acquisitionPlan: community, a community NodeRef, and an entry CName. |
base\open_world\minor_activities\westbrook\japantown\ma_wbr_jpn_13\community\ma_wbr_jpn_013_claws_com.community | How a vanilla communityCommunityTemplate stores generic Tyger Claw entries, phases, appearances, time periods, quantities, and AI-spot references. |
ep1\characters\entities\gang\gang__ep1_tyger_wa.ent | How one installed entity exposes external appearance mappings, internal names, a shared .app, and a root default. Requires Phantom Liberty. |
base\characters\appearances\gang\gang__tyger_wa.app | How one appearance resource stores multiple internal definitions and a WomanAverage frame contract. |
base\quest\side_quests\sq031\phases\sq031_rogue.questphase | How one actor is promoted to Gameplay AI and how direct questUseWorkspotNodeDefinition objects are shaped. |
base\open_world\street_stories\watson\kabuki\sts_wat_kab_02\phases\sts_wat_kab_02_openworld.questphase | How a named community actor receives an AIFollowerRole targeting #player and later receives role clear. |
base\open_world\street_stories\watson\northside_industrial_district\sts_wat_nid_03\phases\sts_wat_nid_03_gameplay.questphase | How six AIPatrolRole payloads reference separate spline paths and explicit patrol parameters. |
Each path supplies Observed in vanilla evidence for its own focused shape. It is not a template for copying unrelated IDs, records, appearances, or world positions.
First-pass failure routing
| Symptom | Inspect first |
|---|---|
Nothing spawns after Activate | Registered block and sectors, quest prefab dependency, area/registry community identity, entry/phase spelling, AI-spot identities, and character record |
| Spawn wait never opens | Whether activation addressed the same community, entireCommunity/count comparison, area streaming, and the requested entry phase |
| Actor exists but the scene cannot bind it | Scene acquisitionPlan, communityParams.reference, communityParams.entryName, and scene start ordering |
| Actor appears at the wrong place or pose | AI-spot placement, exact full spot NodeRef, area spotNodeIds, registry spotNodeRefs, and workspot resource |
| Actor exists logically but is invisible or malformed | Character record entity path, exposed entity appearance, internal .app name, frame/component graph, and saved appearance history |
| Actor is unexpectedly hostile or armed | Character-record base, reaction/faction, action map, archetype, abilities, and equipment |
| Role command fires but the actor does not move | Spawn readiness, AI tier, command target, concrete typed params, path/workspot streaming, and navmesh |
| Actor walks back when the next activity starts | Follower/patrol role was cleared before the next scene, defend, phase, or cleanup owner took over |
| Actor vanishes during the scene | Cleanup edge ordering, scene outcome handoff, and the distance/trigger used before Deactivate |
| A clean resource change behaves like an older build | Starting save, community activation history, scene state, checkpoints, and installed archive filename |
WolvenKit opening every file and ArchiveXL mounting the block are intermediate
checks. Use the Lab 5 clean-save procedure to verify the complete cqa005
activation, acquisition, and cleanup route in game.
Registries and compiled areas
A compact quest community has two world-side owners with different streaming jobs:
AlwaysLoaded sector
worldCommunityRegistryNode
communitiesData[]
worldCommunityRegistryItem
communityId
entriesInitialState[]
template -> communityCommunityTemplateData
Quest sector
worldCompiledCommunityAreaNode_Streamable
sourceObjectId
area -> communityArea
entriesData[] -> phasesData[] -> timePeriodsData[] -> spotNodeIds[]
The registry describes what the community can spawn. The compiled area places the community source in the streamed world and binds its entry/phase periods to world AI-spot identities. Neither substitutes for the other.
Registry responsibility
The inspected mod-owned shape places a worldCommunityRegistryNode in an
AlwaysLoaded sector. Its decisive properties are:
| Property | Responsibility |
|---|---|
communitiesData | Holds one or more worldCommunityRegistryItem records. |
communityId.entityId.hash | Selects the matching compiled community source. |
entriesInitialState | Gives each named entry its initial active state and initial phase. |
template | Embeds communityCommunityTemplateData for the entries available to this registry item. |
workspotsPersistentData | Retains spot identities and initial enabled/placement data in the inspected compact shape. |
An AlwaysLoaded registry is a useful mod-owned arrangement because the
definition remains reachable before the local quest sector finishes streaming.
It does not make the actor AlwaysLoaded. The compiled area and AI spots still
belong to their streamed sector, and activation still requires quest logic when
entryActiveOnStart is false.
Do not elevate the sector category to a universal schema rule. It is the retained, tested arrangement used by this section and must still be checked in the exact new package.
Compiled-area responsibility
The local Quest sector contains a
worldCompiledCommunityAreaNode_Streamable. Its communityArea mirrors the
spawn topology using world identities rather than character records:
entryName
-> entryPhaseName
-> periodName + isSequence
-> spotNodeIds[]
The registry side names spotNodeRefs; the compiled-area side names
spotNodeIds. Those values must resolve to the same placed AI spots. A
community can therefore be internally well-formed yet still fail because the
area points at a different spot identity than the registry template.
Important compiled-area fields in the retained shape are:
| Property | Responsibility |
|---|---|
sourceObjectId | Supplies the community/source identity that joins the area to the registry item. |
area.entriesData[].entryName | Mirrors one registry entry CName. |
phasesData[].entryPhaseName | Mirrors one spawn phase CName. |
timePeriodsData[].periodName | Mirrors the selected period such as Day. |
timePeriodsData[].isSequence | Mirrors whether the period uses its spot set as a sequence. |
timePeriodsData[].spotNodeIds | Names the world-global AI-spot identities used by the period. |
streamingDistance | Participates in when the compiled area is available; it does not guarantee actor readiness at scene start. |
Placement still uses the ordinary streaming-sector chain described in Sector
nodes and placement: one concrete node,
one matching nodeData record, a registered NodeRef where the arrangement uses
one, and the block descriptor that mounts the sector beneath the intended
quest-prefab root.
The three identity joins
Derive each world identity from its own canonical full NodeRef; do not mint
numbers from labels or from a neighbouring identity:
community/source ID
<- RED4 NodeRef hash(full community NodeRef)
registry-node ID
<- RED4 NodeRef hash(full community NodeRef with `_registry`
appended to the final community name)
AI-spot global ID
<- RED4 NodeRef hash(full AI-spot NodeRef)
The hash operation must understand RED4 NodeRef aliases and segments. The #
marker is NodeRef hierarchy/alias syntax and is skipped as a marker by that
operation; it is not an ordinary byte to feed into a generic hash. Do not use
plain FNV-1a over a debug label, the last #name, or the printed full path as a
substitute. When a numeric value must be audited outside normal WolvenKit
serialization, use RED4-aware NodeRef logic such as WolvenKit’s NodeRef
implementation and retain the originating full string beside the result.
Reject zero results and reject every collision across the source, registry, and spot sets before cooking. Equality is required only at the documented source join below; a source/registry collision, source/spot collision, registry/spot collision, or sibling-spot collision is an authoring error.
Community/source identity
In the retained compact shape, these represent one community/source identity and therefore match:
compiled area NodeRef-derived identity
== worldCompiledCommunityAreaNode_Streamable.sourceObjectId.hash
== worldCommunityRegistryItem.communityId.entityId.hash
This equality joins the registry item to its streamed source. It does not mean every nearby 64-bit ID should reuse the same value.
Registry-node identity
The worldCommunityRegistryNode is a separate world node and needs a separate
world-node identity. The historical crashing arrangement reused the
community/source value for that registry node. The repaired candidate assigned
a distinct identity and rejected future collisions.
The rule is not “add one” or “hash the debug name.” Preserve typed values and
derive the registry identity from the canonical full community NodeRef with
_registry appended to its final community name. Let the same RED4-aware
NodeRef path produce all related forms. Record the full synthetic registry
string as well as the serialized number so a review can explain where the
identity came from.
AI-spot identity
Every worldAISpotNode has its own registered world identity. It appears in at
least three roles in the retained shape:
registry template: spotNodeRefs[] full NodeRef
compiled area: spotNodeIds[] worldGlobalNodeID
registry node: workspotsPersistentData[].globalNodeId
That spot identity must be distinct from the community/source identity, the registry-node identity, and every sibling spot. Array position and visible debug text are not identity joins.
Standalone .community resources are comparison sources
The vanilla depot also contains communityCommunityTemplate CR2W resources.
For example:
base\open_world\minor_activities\westbrook\japantown\
ma_wbr_jpn_13\community\ma_wbr_jpn_013_claws_com.community
Observed in vanilla: that resource’s root owns
communityCommunityTemplateData with three communitySpawnEntry objects,
their character records, phases, appearances, time periods, quantities, and
local spot references.
Use it to study template semantics. Do not infer that the inspected mod-owned
worldCommunityRegistryItem.template is a soft path to this file: in the
retained compact registry it is embedded template data. Copying a vanilla
.community resource into a mod archive would also redistribute extracted
game content and preserve identities intended for another activity. Extract
your own reference and author a mod-owned registry instead.
Focused WolvenKit review
For a mod-owned community, inspect these joins without pasting a complete sector into your notes:
- record the full compiled-area NodeRef and its derived source identity;
- confirm the registry item’s
communityIdmatches that source identity; - derive the registry node identity from the full community NodeRef plus
_registry, and confirm it is different; - list each entry and initial phase once;
- derive each world-global AI-spot ID from that spot’s full NodeRef and list both forms;
- confirm the registry
spotNodeRefs, areaspotNodeIds, and persistent spot rows describe the same set; - reject zero values and every source/registry/spot/sibling collision;
- confirm the block mounts both the AlwaysLoaded registry sector and the Quest area/spot sector;
- confirm the questphase declares the prefab root required for its local community reference.
Do not use a numerical sort of the JSON arrays as a shortcut. The decisive
links are NodeIndex, typed IDs, entry/phase names, and NodeRefs.
Common failures
| Symptom | Inspect first |
|---|---|
| Activation node runs but no community appears | Area sector streaming, sourceObjectId/communityId equality, prefab root, and entry initial state |
| One community affects another | Reused community/source identity or copied vanilla identity |
| Loading near the area crashes | Registry-node collision, malformed handles, bad spot IDs, or another resource activated at the same boundary |
| Registry looks correct but the actor never gets a place | Area entriesData/phase/period mirror and exact spotNodeIds |
| Spot exists but is not persistent or enabled as expected | Registry workspotsPersistentData and the spot’s world-global identity |
| Old behavior survives a corrected archive | Return to a save made before any version of the community was installed |
Next: Entries, phases, and AI spots.
Entries, phases, and AI spots
A community entry answers three different questions:
Who can spawn? characterRecordId
In which authored mode? phaseName + appearance + time period
Where can they act? spotNodeRefs -> worldAISpotNode -> workspot resource
Keeping those questions separate makes failures diagnosable. Changing the character record does not move the spot. Moving the spot does not change the appearance. Activating an entry with the wrong phase name does not fall back to a similarly named workspot.
Vanilla references
These focused base-game resources are useful comparisons. Extract them from your own installation; they are references, not files to copy into the project.
base\open_world\minor_activities\westbrook\japantown\
ma_wbr_jpn_13\community\ma_wbr_jpn_013_claws_com.community
Registry-side entry shape
In the retained registry item, template data has this focused shape:
communityCommunityTemplateData
entries[]
communitySpawnEntry
characterRecordId: TweakDBID
entryName: CName
phases[]
communitySpawnPhase
phaseName: CName
appearances[]: CName
timePeriods[]
communityPhaseTimePeriod
hour: CName-like period selector
isSequence: Bool
quantity: Int32
spotNodeRefs[]: NodeRef
The important properties are:
| Property | Meaning in the inspected shape |
|---|---|
characterRecordId | TweakDB record used to create the actor. It can lead to an entity template, appearances, gameplay packages, faction data, and other dependencies; it is not a world identity. |
entryName | Community-local name used by activation and scene acquisition. It is a CName, not the actor’s display name. |
phaseName | Community-local mode selected by initial state or a Spawn Manager action. |
appearances | Names the spawn phase can request from the selected character/template chain. |
hour | Selects the authored time-period row. Day in one fixture is not a rule that communities only work during daytime. |
isSequence | Tells this period whether its spot list represents sequenced use in the inspected model. |
quantity | Requested quantity for that period; it is not a readiness timeout or scene performer count. |
spotNodeRefs | Full or context-valid world references to AI spots. |
entryActiveOnStart, stored under the registry item’s initial state rather
than the spawn entry itself, decides whether quest logic begins with that entry
active. For a controlled quest contact, false plus an explicit activation
edge gives the graph a reviewable owner.
Area-side mirror
The compiled area does not repeat the character record. It repeats the topology needed to bind the entry/phase/time period to placed spot identities:
| Registry template | Compiled area |
|---|---|
communitySpawnEntry.entryName | communityCommunityEntrySpotsData.entryName |
communitySpawnPhase.phaseName | communityCommunityEntryPhaseSpotsData.entryPhaseName |
communityPhaseTimePeriod.hour | communityCommunityEntryPhaseTimePeriodData.periodName |
communityPhaseTimePeriod.isSequence | communityCommunityEntryPhaseTimePeriodData.isSequence |
communityPhaseTimePeriod.spotNodeRefs | communityCommunityEntryPhaseTimePeriodData.spotNodeIds |
The values describe the same intended route through different serialized types. A valid character entry with a stale area mirror can activate but never obtain its intended spot.
When reviewing serialized sectors, do not pair rows merely because they are
adjacent. Follow the entry name, phase name, full spot NodeRef, world-global
spot ID, and sector NodeIndex. Later source snapshots can reorder nodes or
change a workspot without changing a nearby debug string.
worldAISpotNode owns the placed activity
The AI spot is a concrete world node. Its decisive focused structure is:
worldAISpotNode
isWorkspotInfinite
isWorkspotStatic
lookAtTarget
spot -> AIActionSpot
resource -> ResourcePath to .workspot
snapToGround
useClippingSpace
The sector’s nodeData owns its transform and binds the concrete node to the
registered NodeRef. The worldAISpotNode owns the activity configuration. A
workspot resource supplies animation/behavior data; it does not supply the
world position.
| Concern | Owner |
|---|---|
| World position and orientation | The spot node’s matching nodeData placement |
| Quest-prefab child identity | Full/local NodeRef chain and QuestPrefabRefHash |
| Workspot behavior | AIActionSpot.resource depot path |
| Finite versus continuing use | isWorkspotInfinite together with the community period design |
| Ordered movement between spots | A validated sequence using isSequence, multiple spot identities, and compatible AI behavior |
| Actor selection | communitySpawnEntry.characterRecordId |
Do not introduce a patrol merely by adding two points. Sequenced workspots, AI roles, navigation, interruption, combat, and cleanup are separate runtime surfaces. A single stationary contact spot is the smaller Lab 5 search surface.
Character record, entity, and appearance are separate
The community stores a TweakDBID, for example:
Character.jpn_tyger_claws_biker2_ranged2_copperhead_wa
That record is not an .ent depot path. It can refer onward to a character
entity template and other gameplay records. The spawn phase’s appearance
CName must be valid for the selected template chain. An appearance name that
worked for a different record is not inherited because both entries say
default.
For a first contact fixture, prefer a generic population/combat record whose bounded spawn behavior is retained over a story-unique character record. This reduces conflict with vanilla quest ownership, but it does not make every appearance, faction, voice, or AI behavior appropriate. Inspect the selected record and verify the actor in game.
Choosing a workspot
Choose a workspot from evidence tied to the exact record and candidate, then validate it at the new placement. Check:
- the resource exists at the exact depot path;
- the spot transform leaves enough clearance for the actor;
- the surface is reachable and supports the intended ground contact;
- the actor does not clip into walls, props, or the player approach lane;
- the broad setup route streams the area before the scene needs the actor;
- each named pre-scene seed load in Cases 3, 4, and 7 reacquires the same passive contact without duplication;
- normal exit, post-
contact_donereload, completed reload, and delayed deactivation do not leave the actor stuck in the workspot.
Check the workspot at its actual transform. A pose that works at one location can intersect geometry or lack a usable approach at another.
Common failures
| Symptom | Inspect first |
|---|---|
| Entry activates but uses the wrong actor | characterRecordId, entry name, registry item, and saved activation history |
| Actor is invisible or malformed | Requested appearance, record-to-entity path, dependencies, and expansion requirements |
| Actor appears at an unexpected sibling spot | Registry spotNodeRefs, area spotNodeIds, and world-global ID uniqueness |
| Actor clips or faces away | Spot nodeData position/orientation, workspot semantics, clearance, and navmesh |
| Sequence never advances | isSequence, finite/infinite settings, spot ordering, AI behavior, and navigation evidence |
| Scene asks for an entry that exists only in another community | Scene community reference and entry CName; entry names are community-local |
Next: Activation, readiness, and acquisition.
Activation, readiness, and acquisition
Community activation, actor readiness, player approach, and scene acquisition are four separate runtime events. Connecting them in that order prevents the scene from racing the world streamer:
questSpawnManager Activate
-> questCharacterSpawned condition
-> broad setup-area condition
-> scene start
-> scnActorDef community acquisition
The first node requests a state change. The second observes materialization. The third gives world and scene setup an approach boundary. The fourth asks an already-running scene to bind a performer to the available community entry. None substitutes for another.
Vanilla references
These focused base-game resources are useful comparisons. Extract them from your own installation; they are references, not files to copy into the project.
base\quest\minor_quests\mq003\mq003_orbitals.questphase
base\quest\minor_quests\mq003\phases\mq003_homeless.questphase
base\quest\minor_quests\mq003\scenes\mq003_01_homeless.scene
Activate requests community state
A questphase uses questSpawnManagerNodeDefinition. Its actions array can
contain a questSpawnManagerNodeActionEntry whose typed payload is
questCommunityTemplate_NodeType:
questSpawnManagerNodeDefinition
actions[]
questSpawnManagerNodeActionEntry
type -> questCommunityTemplate_NodeType
action: Activate
spawnerReference: NodeRef
communityEntryName: CName
communityEntryPhaseName: CName
spawnerReference must resolve to the intended community source. The two
CName fields decide whether the command addresses a named entry/phase or the
broader community operation used by the inspected vanilla pattern.
None is an authored CName, not a missing field, null, or an empty string.
The whole-community action uses Activate None/None; a focused action names
the community-local entry and phase.
Choose scope deliberately:
| Action scope | Use when | Review risk |
|---|---|---|
Whole community (None / None) | The lifecycle owner intentionally brings the configured community into its active state | It may activate more entries than the scene needs |
| Named entry and phase | One entry must move to one explicit authored phase | A spelling mismatch can leave the intended actor inactive |
An Out edge from the Spawn Manager says the command was issued. It does not
prove the streamed area is present, a spot is available, an entity was
constructed, or a scene can acquire that entity.
CharacterSpawned observes readiness
The focused retained condition shape is:
questPauseConditionNodeDefinition
condition -> questCharacterCondition
type -> questCharacterSpawned_ConditionType
objectRef.reference: NodeRef
comparisonParams
comparisonType: Greater
count: 0
entireCommunity: 1
Read this payload literally. With Greater, count 0, and
entireCommunity: 1, the condition asks whether the spawned count evaluated
at community scope is greater than zero. It does not mean “every configured
actor is ready.” For a one-entry contact, that distinction is small. For a
three-entry encounter, it is decisive.
The objectRef.reference must target the same community intended by the
activation action. Do not substitute the registry node’s world-global ID, the
AI spot NodeRef, the entry CName, or the character TweakDBID. Those belong
to different identity domains.
If a scene requires several specific performers, prove every required actor
instead of treating aggregate > 0 as enough. Parallel actor- or entry-scoped
spawn waits converging on an explicit And rendezvous make that requirement
visible in the graph. This follows from the comparison semantics; it is not a
claim that every vanilla multi-actor scene uses that exact graph. Check a
vanilla resource with the same actor count and lifecycle before treating the
arrangement as a reusable precedent. The single-entry cqa005 contact uses the
same ordering and is exercised by the Lab 5 test guide.
Broad setup is an approach boundary
Readiness answers whether the actor exists. It does not answer whether V has approached through a useful streaming corridor or whether scene setup has had time to establish its state.
Use a generous outer trigger as the scene-start boundary after readiness:
Activate issued
-> spawned readiness true
-> V enters broad setup area
-> checkpoint if the phase owns one
-> start scene at its named input
The exact radius is location-specific. It must be supported by measured approach routes, streaming distance, sight lines, and the scene’s intended staging. A number retained from another location is not evidence for the new one. See Triggers and areas for volume and edge semantics.
Keep narrower mood, awareness, greeting, or interaction gates inside the already-running scene when that is the intended presentation. If the scene is launched only after the narrowest gate is true, all earlier setup conditions may already be satisfied on its first update. That can collapse setup, AI ownership, and opening dialogue into one startup tick.
The broad setup edge is not another spawn command. It is a sequencing and streaming boundary between the readiness proof and scene initialization.
The scene acquires; it does not spawn
A community-backed scene actor uses this focused shape:
scnActorDef
acquisitionPlan: community
actorId: scnActorId
actorName
communityParams -> scnCommunityParams
reference: NodeRef
entryName: CName
The fields cross three contracts:
| Field | Contract |
|---|---|
acquisitionPlan: community | Tells the scene to bind through the community system rather than spawn/despawn or context lookup |
communityParams.reference | Names the community source the quest activated and waited for |
communityParams.entryName | Selects the community-local entry the performer represents |
actorId | Supplies the scene-local performer identity used by sections and events |
actorName | Supplies scene authoring/debug context; it is not the community join key |
The scene’s entryName must exactly match the registry template entry and the
compiled area’s entry mirror. The reference must resolve in the scene’s
NodeRef context to the same community used by the questphase. A character can
be visible in the world while acquisition still fails because either join is
wrong.
Scene acquisition also does not transfer permanent ownership. The quest still owns activation and cleanup; the scene owns the actor only for the performance window. Let the scene produce a named outcome before the quest advances to cleanup. The Scenes section owns the screenplay, performer, entry-point, and exit details.
Authoring and review procedure
For one community-backed contact:
- Confirm the registry item and streamed area use the intended community identity and that the area can stream on every supported approach.
- Add a Spawn Manager action using the community source NodeRef and chosen whole-community or named-entry scope.
- Add a separate
CharacterSpawnedpause condition. Inspect the comparison, count,entireCommunity, and object reference rather than relying on the node caption. - Route readiness to a broad setup-area condition. Do not duplicate the activation action on every approach branch.
- Start the scene through the exact named input after readiness and setup are both satisfied.
- Set every community-backed
scnActorDeftoacquisitionPlan: communitywith the matching community reference and entryCName. - Route each named scene outcome back into explicit quest progression before scheduling cleanup.
For multiple required performers, repeat step 3 at the necessary scope and
join the waits explicitly. Do not infer “all ready” from a single > 0
community count.
Acceptance matrix
Test the complete join in game, not only in WolvenKit:
The Lab 5 cases cover the supplied pre-scene save and the separate near-site arrival save. Generic fast-travel arrival, arbitrary pre-scene save points, active-line/interruption reload, and alternate named outcomes are additional tests for a quest that uses them.
| Route | Required observation |
|---|---|
| Case 1: outside original clone, ordinary approach | Actor materializes before scene acquisition; setup occurs once |
Case 2: separate original-near-setup clone, immediate entry | Spawned readiness still wins the race before setup advances |
Cases 3/4/7: distinct clones of seed-pre-scene-outside-setup | The exact saved child/contact state reacquires; Cases 3/4 start once and Case 7 survives its specified stream-away/return route |
Case 9: seed-completed clone | No duplicate scene start or accidentally reactivated contact |
| Supplemental: generic fast travel, then approach | Same ordering; no start-time acquisition failure |
| Supplemental: arbitrary pre-scene save point, then load | No stale activation or skipped readiness edge; no duplicate setup or skipped readiness |
| Supplemental: complete alternate named outcomes | Each route writes the intended durable progression before cleanup |
Record the installed installed archive name, starting save, approach route, game and tool versions, exact first and last visible behavior, and any crash or log artifact. A successful CR2W round trip is Structurally validated; it is not a runtime result.
Common failures
| Symptom | Likely contract failure |
|---|---|
| Spawn Manager completes but no actor appears | Activation is only a request; inspect streaming, entry/phase names, area/registry join, spot, and character record |
| Readiness gate opens with only one of several actors present | Aggregate Greater 0 was mistaken for an all-performers condition |
| Readiness never opens | Wrong reference/scope, inactive entry, unavailable spot, bad area placement, or dirty save |
| Scene starts as V crosses the innermost trigger and immediately misbehaves | Broad setup and narrow engage boundaries were collapsed |
| Actor is visible but scene has no performer | Wrong acquisition plan, community reference, entry name, or scene start context |
| Scene restarts on return | Completion state did not gate the setup/start path |
Next: Cleanup and character safety.
Cleanup and character safety
Community cleanup is a quest-owned lifecycle transition, not the last line of a scene. Preserve the actor until the scene has exited, its outcome has been consumed, the relevant journal or branch state has advanced, and V has left a generous cleanup area:
scene named exit
-> consume outcome in questphase
-> advance outcome-specific journal or branch state
-> wait until V exits cleanup area
-> questSpawnManager Deactivate
-> complete cleanup branch
-> write terminal one-shot state where the owning phase requires it
This ordering protects the scene, AI, and workspot from losing their actor while they may still own it. It also gives reload tests an explicit durable state to resume from.
Deactivate is a command, not a disappearance proof
Cleanup uses the same Spawn Manager family as activation:
questSpawnManagerNodeDefinition
actions[]
questSpawnManagerNodeActionEntry
type -> questCommunityTemplate_NodeType
action: Deactivate
spawnerReference: NodeRef
communityEntryName: CName
communityEntryPhaseName: CName
The action scope matters just as it does for Activate. A whole-community
deactivation intentionally uses the broad None / None scope seen in the
inspected pattern; a targeted action must name the exact entry and phase
contract expected by that resource.
The Spawn Manager’s outgoing edge establishes graph progression after issuing the command. It does not prove that the actor vanished during the same frame. Streaming, AI, scene, combat, workspot, and engine lifecycle rules can affect when the actor is released. Runtime acceptance must observe both the graph outcome and the world result.
Delay cleanup past every active owner
Do not deactivate while any of these can still be true:
- the scene has not reached a named exit;
- a screenplay or section event still references the performer;
- AI or a workspot still owns the actor for immediate presentation;
- combat is active or the actor is reacting to damage;
- the player remains close enough to watch or interact with the actor;
- durable quest/journal progression has not been written;
- a reload could resume before the cleanup branch knows which outcome won.
A simple safe pattern is an outer cleanup trigger whose Exited edge becomes
eligible only after the named scene outcome and progression writes. Choose the
volume from measured routes and sight lines. It should be generous enough that
normal departures release presentation ownership before the deactivation
request, but not so broad that the cleanup branch never runs during a typical
playthrough.
Do not use a timer alone as proof of safety. A player can pause, enter combat, save/reload, or remain beside the actor longer than the assumed duration. A spatial exit combined with explicit quest state describes the actual requirement more clearly.
Advance the outcome before cleanup; finish terminal state afterward
The community is presentation/runtime state. Journal and branch state make the scene outcome explicit before cleanup, while the owning parent can reserve its terminal one-shot fact until cleanup has completed.
For every scene outcome:
- route the named scene output into the questphase;
- write any outcome or choice fact the branch actually owns;
- advance or close the applicable journal objective so cleanup does not erase the only visible result;
- converge only branches that have equivalent cleanup requirements;
- wait for the safe spatial exit;
- issue the intended
Deactivateaction; - complete the child/root branch according to its explicit output contract;
- write the terminal completion fact at the point defined by that owner.
If cleanup runs before the outcome-specific state, a save can retain an absent
actor without retaining the progression needed to resume the encounter. Do
not infer that every fact must therefore precede cleanup. In exact cqa005,
the scene output succeeds the meet objective, retires the pin, and activates
the leave objective before the outer wait; deactivation and leave success then
return the child, and the root writes cqa005_completed afterward. That order
is the candidate’s explicit ownership contract.
Community activation, scenes, facts, journal entries, and persistent actor state are save-backed surfaces. Use a clean pre-activation save after changing their identities or topology. See Persistent state and Quest state.
Do not borrow a unique story character
A story-unique character record may already be owned by vanilla quest logic, availability facts, appearances, phone/scene systems, or persistent save state. Reusing it for a convenient contact can create conflicts that a clean CR2W round trip cannot reveal:
- the character may be unavailable, dead, hidden, or in another phase;
- a vanilla scene may acquire or reposition the same identity;
- save state may retain an appearance or attitude inappropriate for the mod;
- deactivating the mod community can interfere with another lifecycle owner;
- a test save can make the character appear reliable when a different save legitimately cannot provide it.
Bounded generic-character starting point
For the Lab 5 stationary contact, use this pair as the evidence-backed starting point:
Character record:
Character.jpn_tyger_claws_biker2_ranged2_copperhead_wa
Workspot resource:
base\workspots\common\ground\generic__stand_ground_cigarette__smoke__01.workspot
Why this pair is preferred:
- the character is a generic Tyger Claw record, reducing unique-story ownership conflicts;
- Observed in vanilla: the community at
base\open_world\minor_activities\westbrook\japantown\ma_wbr_jpn_13\community\ma_wbr_jpn_013_claws_com.communityuses the record in a native community entry; - the lab’s AI spot selects the cigarette workspot explicitly.
This is not a promise that the actor will stand safely at a new transform, provide an appropriate voice, remain passive, acquire into a new scene, or clean up correctly. Lab 5 tests ordinary passivity, acquisition, and cleanup for its supplied contact. Voice casting, workspot and facial-animation quality, combat, and interruption need their own checks.
How to verify workspot mappings
Follow the typed entry name → registry spotNodeRefs → area spotNodeIds →
concrete worldAISpotNode resource path. Do not pair nearby serialized arrays
or debug strings by position. Inspect the installed resource when the project
and game appear to select different workspots.
Interruption and combat policy
A community-backed contact needs an authored answer for interruption. At minimum, decide what happens if V attacks, draws police or gang combat into the area, leaves during the scene, or reloads while the actor is active.
Do not make “deactivate immediately” the generic interruption response. An actor disappearing during combat or while scene code holds a performer can be more damaging than leaving the actor present until a safe terminal edge. Instead, route interruption to a named scene/quest outcome where possible, write durable state, release scene ownership, then reuse the delayed cleanup branch.
The generic record already carries gameplay and faction behavior. It is not a neutral dialogue mannequin. Attitude, combat target, voice, facial animation, appearance, and invulnerability are separate authoring decisions requiring their own evidence and acceptance tests.
Clean-save acceptance matrix
Use a clean save made before the community was ever activated for the first identity/topology test. Then cover all lifecycle boundaries:
This design matrix is broader than the supplied Lab 5 cases. The generic pre-scene row below means any arbitrary or unlisted active-child save point; active-scene reload, alternate outcomes, interruption, and combat require additional testing if your quest uses them.
| Starting point | Action | Required observation |
|---|---|---|
| Clean pre-activation save | Approach, complete normal outcome, leave cleanup area | Actor remains through named scene exit; progression writes; cleanup issues once |
| Clean pre-activation save | Approach, choose every alternate outcome, leave | Each branch records its own durable result before shared cleanup |
Named seed-pre-scene-outside-setup (Cases 3/4/7) | Load a distinct byte-identical clone, then follow the frozen case route | Exact readiness/acquisition state resumes without a duplicate actor; the named case completes as specified |
| Arbitrary/unlisted active actor before scene | Save/load at another point, then enter broad setup area | Supplemental: readiness/acquisition resumes without duplicate actor or scene |
| During scene at a supported save point | Save/reload and finish | Performer remains coherent; named outcome still reaches progression |
Named seed-post-contact-inside-cleanup (Cases 5/6/8) | Load a distinct byte-identical clone, then follow the frozen case route | Progression is retained and delayed deactivation occurs once |
Named seed-completed (Cases 9/11) | Load a distinct byte-identical clone under the case-specific installation | Scene does not restart and no stale contact reactivates |
| Active contact | Trigger intended interruption/combat cases | No mid-owner disappearance, deadlock, duplicate actor, or lost outcome |
For each run, record the installed archive filename, starting save, route, chosen outcome, first/last visible actor state, journal/fact state, and behavior after reload and revisit. A later run against a different archive cannot silently inherit the earlier result.
Common failures
| Symptom | Inspect first |
|---|---|
| Actor disappears before the scene finishes | Deactivate edge, named-exit routing, workspot/AI ownership, and cleanup trigger polarity |
| Actor remains forever after completion | Whether the cleanup-area Exited edge was armed, action scope/reference, and saved branch state |
| Scene restarts although the actor was removed | Durable completion fact/journal gate and setup/start path |
| Different saves produce different actor availability | Unique-character ownership, old community activation, scene state, death/appearance state, and starting-save history |
| Actor spawns but behaves like a hostile gang member | Character record packages, faction/attitude logic, AI commands, and interruption policy |
| Workspot evidence appears contradictory | Installed archive filename, typed spot joins, source-to-binary synchronization, and whether the candidate was stationary or patrolling |
Return to Communities and characters.
Character records, entities, and appearances
This chapter follows the native identity chain behind a community character. Its practical outcome is a reviewable, mod-owned character definition whose record, entity template, exposed appearance mapping, and internal appearance definition agree before the character is introduced into quest logic.
It is an advanced authoring boundary, not a promise that an arbitrary set of meshes will form a production-ready NPC. Rig compatibility, garment support, materials, deformation, facial animation, LODs, censorship, and streaming all remain runtime surfaces.
Prerequisites and tested baseline
Read Entries, phases, and AI spots, Activation, readiness, and acquisition, and Persistent state first. Use a separate WolvenKit project for extracted comparison resources so that a vanilla file cannot enter the distributable project by accident.
The focused inspection and structural checks on this page use:
- Cyberpunk 2077 Windows GOG
2.31a, with Phantom Liberty for the citedep1\...entity; - WolvenKit
8.19.0; - ArchiveXL
1.27.0, RED4ext1.30.0, and redscript0.5.31for the wider quest package; and - TweakXL
1.11.3when adding a mod-ownedCharacter.*record.
TweakXL is an authoring and loading mechanism for TweakDB changes. The engine
model described here remains the native Character record and its referenced
resources. A character that only reuses an existing record does not need a new
TweakXL record, but it also inherits that record’s ownership and behavior.
Vanilla references
The generic Tyger Claw record used in this chapter resolves through these focused resources. Extract them into a separate research project:
ep1\characters\entities\gang\gang__ep1_tyger_wa.ent
base\characters\appearances\gang\gang__tyger_wa.app
The native resource chain
The community does not spawn an .app file directly:
communitySpawnEntry.characterRecordId TweakDBID
-> Character.<record>
-> entityTemplatePath ResourcePath to .ent
-> baseline reaction / faction / AI / equipment records
-> entEntityTemplate
-> defaultAppearance exposed mapping CName
-> appearances[]
entTemplateAppearance.name exposed mapping CName
entTemplateAppearance.appearanceName internal .app CName
entTemplateAppearance.appearanceResource ResourcePath to .app
-> appearanceAppearanceResource
-> baseEntityType body-frame contract
-> appearances[].appearanceAppearanceDefinition.name
-> components[] + compiledData render/runtime graph
-> meshes, morph targets, rigs, materials, animations, and support data
An appearance request therefore succeeds only when every boundary resolves.
The same visible word can occur in several layers, but the values do not become
interchangeable merely because they are all stored as CName.
Keep the identifier domains separate
| Value | Type and owner | What it selects |
|---|---|---|
Character.cqa_contact | TweakDBID in TweakDB | Character gameplay record |
mod\cqa\characters\contact\contact.ent | ResourcePath on the record | Root entity template |
cqa_contact_default | CName on entTemplateAppearance.name | Appearance exposed by the entity |
default | CName on entTemplateAppearance.appearanceName and the .app definition | Internal appearance payload |
mod\cqa\characters\contact\contact.app | ResourcePath on the entity mapping | Appearance resource |
| Component names and bind names | CName values inside the appearance graph | Individual runtime components and attachment relationships |
| Community entry and phase names | Community-local CName values | Which actor definition and mode the community requests |
| Community and AI-spot references | NodeRef / world identity values | The spawned instance’s world ownership and position |
Scene actorId | Scene-local ID | Performer references inside one scene |
Do not use the character record, entity path, entry name, exposed appearance, or scene actor ID as substitutes for one another. Record each typed value in the resource inventory even when a UI label shortens it.
The character record owns gameplay defaults
A Character record is more than a path to geometry. The installed schema
includes fields for the entity template, appearance selection, voice,
reactions, senses, faction and attitude, action map, archetype, equipment,
abilities, stats, loot, object actions, UI presentation, and persistence
priority.
For quest work, review at least these inherited surfaces:
| Record surface | Why it matters to a quest |
|---|---|
$base inheritance | Pulls in a large behavior and interaction baseline; Quest_NPC_Base and a gang gameplay base are not neutral equivalents. |
entityTemplatePath | Must resolve to the intended mod-owned .ent or an explicitly accepted game dependency. |
appearanceName | Can participate in the requested exposed appearance; it is not the internal .app definition by default. |
reactionPreset, baseAttitudeGroup, affiliation | Influence passivity, hostility, reactions, and other systems outside the quest graph. |
actionMap, archetypeData, abilities | Supply gameplay AI actions and capabilities; assigning a quest role does not erase them. |
| equipment and loot | Can arm the actor and create drops or rewards the quest did not intend. |
voiceTag and audio fields | Join later spoken/audio systems; a visible actor is not proof that casting is correct. |
| display-name fields | Join localization/UI; they are not community or scene identities. |
| savable/priority behavior inherited from the base | Can change reload and lifecycle behavior and therefore needs clean-save testing. |
The cited Tyger Claw record is useful because its exact spawn lineage is retained. It is not a neutral contact: its inherited faction, aggression, weapons, loot, action map, archetype, and abilities remain part of the actor. Prefer a purpose-authored record for a recurring story character, and make its base-class choice an explicit design decision.
The entity exposes appearance mappings
An entEntityTemplate owns the runtime entity scaffold and a list of
entTemplateAppearance mappings. A focused mapping has three decisive values:
entTemplateAppearance
name: cqa_contact_default
appearanceName: default
appearanceResource: mod\cqa\characters\contact\contact.app
name is the exposed selection used by the entity/default/world-facing
contract. appearanceName chooses one definition inside the referenced
appearance resource. The two may intentionally differ.
The entity also owns its default exposed selection, runtime entity class, root components, includes, bindings, and compiled representation. A root built for one body frame is not converted into another frame by changing a single appearance name. The retained research male entity has 110 root components; the installed EP1 Tyger woman entity has a different graph. Those counts are observations, not targets to copy.
The appearance owns the renderable component graph
An appearanceAppearanceResource contains one or more
appearanceAppearanceDefinition objects. A definition can own skinned and
static mesh components, facial and animation setup, garment support, shadows,
dangle/physics relationships, chunk masks, bind names, visual tags, proxy and
LOD data, and resolved dependencies.
The focused properties to audit are:
| Property | Contract |
|---|---|
baseEntityType | Declares the expected entity/body frame such as ManAverage or WomanAverage. |
definition name | Must match the entity mapping’s internal appearanceName. |
components[] | Authored component graph, including resources, bindings, enabled state, and visual settings. |
compiledData | Cooked/runtime representation associated with the appearance; do not assume an edit to the visible component list regenerated it. |
resolvedDependencies and resource fields | Must resolve to installed game assets or mod-owned paths that the package actually ships. |
| mesh appearance, chunk mask, bind and skinning values | Select presentation inside each component; a valid mesh path alone is insufficient. |
The installed Tyger .app demonstrates that one appearance resource can serve
many internal definitions. The retained mod-owned pair demonstrates the
smaller one-definition arrangement. Neither is a magic template. Do not copy
the extracted Tyger resource into a mod or redistribute any of its binary
dependencies.
Manual WolvenKit inspection
Use a comparison project that will never be packed:
- In WolvenKit’s Asset Browser, find the exact entity and appearance depot
paths cited above. Confirm that the entity comes from
ep1and record that dependency. - Extract or add them only to the comparison project. Open the
.entin the CR2W editor and inspectRootChunk,defaultAppearance, andappearances[]. - For one mapping, record the exposed
name, internalappearanceName, and fullappearanceResourcepath. - Open that
.app. ConfirmRootChunkisappearanceAppearanceResource, inspectbaseEntityType, and locate the exact internal definition bynamerather than array position. - Inspect a few decisive components and their compiled counterparts. Follow their resource paths locally; do not paste the complete resource or copy it into the distributable project.
- In WolvenKit’s Tweak Browser, inspect the selected
Character.*record and its inheritance. Record the effective entity path, appearance/default, reaction, faction, action map, archetype, equipment, abilities, voice, and expansion requirements. - Close the comparison project and verify that none of its extracted game-owned resources appear in the distributable project’s packed file list.
This is Observed in vanilla inspection. It becomes neither permission to redistribute the files nor runtime proof for a new character.
Manual authoring boundary
Author under a unique namespace, for example:
archive\mod\cqa\characters\contact\contact.ent
archive\mod\cqa\characters\contact\contact.app
r6\tweaks\cqa\character_contact.yaml
Use a mod-owned, redistributable entity/appearance shell that is compatible with the intended body frame. Do not turn an extracted vanilla CR2W into the shipping shell. Building a complete NPC entity scaffold from an empty CR2W is outside the retained evidence; if no legal shell exists, keep the character Experimental and create a dedicated resource fixture before integrating it into a quest.
In the WolvenKit CR2W editor:
- Open the mod-owned
.entand reduce the first test to one exposed appearance mapping. - Set a unique exposed
name, pointappearanceResourceat the mod-owned.app, and setappearanceNameto the exact internal definition. - Set
defaultAppearanceto that exposed name. Remove or rename stale shell mappings that can be selected accidentally. - Open the mod-owned
.app, confirm the frame-compatiblebaseEntityType, and give the one test definition the intended internal name. - Add or change one reviewed component bundle at a time. Preserve the component class, bind/skin target, companion pieces, chunk mask, mesh appearance, animation/physics support, and both authored and compiled representations required by the shell.
- Save, reopen, and inspect both files. A successful save is only a Structurally validated intermediate check after a WolvenKit serialize/deserialize round trip.
Then add the mod-owned TweakDB record with TweakXL. At minimum, explicitly
review its unique record name, inherited base, entityTemplatePath, display
localization, affiliation/attitude policy, voiceTag, and any appearance
selection. Add combat archetype, action map, abilities, equipment, stats, and
loot only when the quest actually needs them. Do not inherit a gang or boss
record merely to obtain one visual.
Finally, put the new record’s TweakDBID in the community entry and make the
community phase request the intended exposed appearance contract. Keep the
existing community, world, readiness, and cleanup identifiers unchanged for
the first isolated character swap; changing all layers at once destroys the
failure boundary.
Structural acceptance before launch
Before packing, make a compact inventory and reject the build unless:
- the TweakDB record name and every mod depot path are unique;
- the record’s
entityTemplatePathresolves to the packed.ent; defaultAppearanceis present in the entity’s exposed mapping set;- every exposed mapping has a non-empty internal name and a resolvable
.app; - every internal name exists exactly once in the referenced
.app; - entity frame, appearance
baseEntityType, rigs, skinning targets, and component bundles agree; - no generated or edited file overrides a game-owned depot path;
- every game-owned dependency is referenced by path rather than redistributed;
- expansion-only paths are declared as requirements;
- WolvenKit reopens the cooked resources without dropping handles, components, mappings, or dependencies; and
- the final archive contains only the intended mod-owned resources.
ArchiveXL mounting, TweakXL loading, WolvenKit reopening, and a clean log are necessary diagnostics. They do not prove that the puppet renders or behaves.
Clean-save and lifecycle matrix
Character records, community actors, death/defeat state, scene ownership, and appearance/lifecycle state can interact with the save. After changing a record name, entity path, exposed mapping, internal definition, community identity, or actor topology, begin from an untouched pre-activation save.
| Start | Action | Required observation |
|---|---|---|
| Clean pre-activation save | Activate only the character’s community | Exactly one actor spawns with the intended exposed appearance and no invisible logical puppet |
| Same clean baseline clone | Approach from each supported route | Correct LODs, materials, hair/clothing behavior, facing, collision, and no late component pop |
| Active actor | Stream out and return | Same record, appearance, equipment, and community entry return without duplication |
| Supported pre-scene save | Reload, then start the scene | Scene acquires the same actor; facial/body animation and voice casting remain coherent |
| Active actor in intended passive/combat cases | Exercise reactions and damage policy | Faction, attitude, equipment, archetype, mortality, and loot match the quest design |
| Post-outcome save | Reload, leave the cleanup boundary, revisit | Outcome persists; actor releases once; no stale appearance or reactivation |
| Completed save | Remove the mod archive and load only a disposable clone | Failure is isolated and understood; never use a valuable save for removal testing |
Record the installed archive name, TweakXL filename, complete version set, starting save, community/entry/appearance names, route, and first/last visible state for each run. Promote only the exact in-matrix artifact.
Common failures
| Symptom | Inspect first |
|---|---|
| Logical actor exists but is invisible | Requested exposed name, entity mapping, internal .app name, resource path, component enabled state, and compiled representation |
| Appearance from another NPC appears | Record inheritance, appearanceName, entity default, community phase appearance, dirty-save history, and mapping-name collision |
| Body, clothing, or hair is malformed | baseEntityType, rig/skin target, bind names, garment support, chunk masks, companion components, and frame compatibility |
| Actor is unexpectedly hostile or armed | Record base, reaction preset, attitude/faction, action map, archetype, abilities, and equipment |
| Actor works until stream-out or reload | Persistent identity, community lifecycle, entity dependencies, LOD/proxy data, and clean starting save |
| Scene has the actor but face/body animation fails | Rig and facial setup, scene performer binding, animsets, voice/lipsync resources, and whether the body frame matches the authored performance |
| Package replaces a vanilla NPC globally | A game-owned CR2W was shipped or a shared vanilla record was mutated instead of adding mod-owned paths and records |
Next: AI roles, behavior, and workspots.
AI roles, behavior, and workspots
This chapter composes advanced character behavior without pretending that one AI node owns the actor. Its practical outcome is an explicit ownership timeline from community spawn, through gameplay AI and a temporary role or workspot, into scene or cleanup handoff.
The key distinction is:
character record supplies baseline capabilities and reactions
community supplies the actor instance and ambient phase
Puppet AI Manager selects an active AI tier
AI command assigns a temporary high-level role
world path or workspot supplies a destination/activity
quest conditions observe the required result
scene or cleanup becomes the next lifecycle owner
A role is not navigation, a workspot is not a character record, and the output of a command is not proof that the actor reached the destination.
Prerequisites and tested baseline
Read Character records, entities, and appearances, Activation, readiness, and acquisition, Cleanup and character safety, and Signal flow first.
The practical inspection and authoring boundary uses Cyberpunk 2077 Windows
GOG 2.31a, WolvenKit 8.19.0, ArchiveXL 1.27.0, RED4ext 1.30.0, and
redscript 0.5.31. If baseline character behavior is changed through a new
record, the tested chapter-specific loader is TweakXL 1.11.3.
Use a clean save made before community activation whenever you change the actor reference, AI tier, role subtype, path/workspot reference, community phase, record behavior, or cleanup topology.
Five behavior owners
| Owner | What it contributes | What it does not prove |
|---|---|---|
Character.* TweakDB record | Reaction preset, faction/attitude, action map, archetype, abilities, equipment, stats, loot, and other gameplay defaults | A spawned instance, a route, or quest ownership |
| Community entry and phase | Actor instance, requested appearance, schedule/quantity, and ambient AI-spot assignment | Gameplay-tier readiness or scene acquisition |
questPuppetAIManagerNodeDefinition | Requested story/AI tier for one or more entity references | That the actor spawned, navigated, or finished a role |
questMiscAICommandNode and typed params | A high-level scripted command such as assigning or clearing a role | Navmesh, path existence, arrival, or safe interruption |
| AI spot, spline, or workspot | Placed activity, route reference, animation/behavior resource, and transform | Actor identity, baseline hostility, or durable quest outcome |
Scene acquisition is a sixth, temporary owner. Do not let a gameplay role, ambient workspot, and scene performance fight for the same actor. Establish a named handoff edge and verify the actor’s state on both sides.
Entity references select the runtime actor
The focused quest AI nodes use gameEntityReference rather than the character
record’s TweakDBID:
gameEntityReference
type: EntityRef
reference: <community or actor NodeRef>
names[]: <optional community entry CName values>
dynamicEntityUniqueName
sceneActorContextName
slotName
For the vanilla follower comparison, the command target is
reference: #kab_02_com_chester plus names: [chester]; the follower role’s
target is a separate reference, #player. The first selects the actor receiving
the role. The second selects whom that actor follows.
The reference and names array form one typed selector. An empty names list
can be correct for a direct actor NodeRef, while a community reference can need
the exact entry name. Do not add, remove, or reorder names based on a graph
caption. Compare the full focused node and confirm the intended multiplicity.
Promote the actor before issuing gameplay commands
The observed sq031 shape is compact:
questPuppetAIManagerNodeDefinition
entries[]
questPuppetAIManagerNodeDefinitionEntry
entityReference.reference: #rogue
aiTier: Gameplay
The retained escort uses the same family before assigning its follower role. A safe sequence is:
community Activate
-> wait CharacterSpawned for the intended entry
-> Puppet AI Manager: Gameplay
-> assign the role or workspot command
-> observe actor position/state with separate conditions
Activate is a spawn request, CharacterSpawned is readiness, the AI manager
requests a tier, and the command requests behavior. Keep all four nodes visible
in review. Do not use a delay as a substitute for actor readiness.
Changing tier can affect presentation, scene ownership, and combat behavior.
The page does not prescribe other gameStoryTier values. Extract the exact
vanilla context and test any tier transition independently.
Assign and clear a follower role
The role assignment is carried by the concrete params handle:
questMiscAICommandNode
entityReference: <target community/entry>
params -> AIAssignRoleCommandParams
role -> AIFollowerRole
followerRef.reference: #player
The corresponding clear uses:
questMiscAICommandNode
entityReference: <same target community/entry>
params -> AIClearRoleCommandParams
In the inspected vanilla follower and patrol nodes,
questMiscAICommandNode.function still contains
AIClearRoleCommandParams even when the concrete params handle is
AIAssignRoleCommandParams. The WolvenKit class default has the same function
value. Therefore, inspect both fields and treat the concrete handle graph as
decisive evidence for the serialized role payload; do not rename function
to an invented value based on the desired action caption.
For a new node, compose the focused native shape in the CR2W editor and reopen it after saving. Do not copy a whole vanilla phase. Preserve the exact typed handle nesting, target reference, role subtype, and sockets while replacing only the identities the new quest owns.
The follower role does not author catch-up teleport, elevators, doors, restricted nav areas, companion combat policy, or destination completion. Use actor-position conditions at explicit route gates and keep the role until the next owner is ready.
Patrol roles point at a separate path contract
The inspected sts_wat_nid_03 nodes use:
AIAssignRoleCommandParams
role -> AIPatrolRole
pathParams -> AIPatrolPathParameters
path: <spline NodeRef>
movementType: Walk
continuationPolicy: FromNextControlPoint
startFromClosestPoint: true
patrolWithWeapon: false
isBackAndForth: true
isInfinite: true
numberOfLoops: 1
sortPatrolPoints: true
patrolAction: PatrolActions.DefaultPatrolAction
Those values are Observed in vanilla for the six inspected assignments. They are not recommended defaults for every patrol. In particular, an infinite role and a loop count can coexist in the serialized object; do not guess which field wins for a new combination.
The path is a NodeRef to another world-owned resource. It must resolve in
the mounted prefab/sector context, and the route must be navigable for the
selected actor. The role cannot manufacture navmesh or stream the path’s
sector. Test one actor and one path before adding squads, alert variants, or
combat.
For finite movement, author an explicit observation or completion policy.
questCharacterRoleFinished_ConditionType exposes an actor objectRef and an
AIFiniteRoleType selector in the current schema, but this repository has no
retained runtime fixture for its exact already-finished, interruption, or
reload behavior. Using it is Experimental until reduced and tested.
Ambient AI spots and quest-driven workspots are different
A community phase can place an actor at a worldAISpotNode:
community phase spotNodeRefs
-> compiled-area spotNodeIds
-> worldAISpotNode
spot -> AIActionSpot
resource -> .workspot
nodeData -> world transform
That is ambient community ownership. It is the right small surface for a stationary contact that should already be posed before the player arrives.
A quest can also issue a direct workspot command to an existing actor. The
inspected sq031 node has this focused shape:
questUseWorkspotNodeDefinition
entityReference.reference: #sq031_com_longue_male_01
paramsV1 -> questUseWorkspotParamsV1
function: UseWorkspot
workspotNode: #sq031_ws_longue_male_01
movementType: Walk
teleport: true
finishAnimation: true
changeWorkspot: true
isWorkspotInfinite: true
repeatCommandOnInterrupt: true
continueInCombat: false
outputs: Work Started, Success
cut: CutDestination
That is Observed in vanilla for the cited node. It does not prove that
these flags are appropriate together for another actor or that either output
has the completion semantics a new quest expects. An infinite workspot, an
animation-finishing flag, and both Work Started/Success sockets must be
tested as a complete contract; do not infer arrival or animation completion
from their names alone.
Choose one owner deliberately:
| Desired behavior | Smaller control surface |
|---|---|
| Actor spawns already standing/smoking/guarding | Community phase plus placed worldAISpotNode |
| Quest orders a spawned actor into a specific placed activity | questUseWorkspotNodeDefinition plus separate state observation |
| Actor follows V through ordinary space | Gameplay tier plus AIFollowerRole |
| Actor traverses an authored spline | Gameplay tier plus AIPatrolRole and path parameters |
| Actor performs a scene | Release or coordinate AI/workspot ownership, then let the scene acquire the performer |
Do not layer all of them on the actor and hope the latest command wins.
Commands and conditions have separate jobs
The outgoing edge of an AI manager, role node, or workspot node proves graph progress according to that node’s output contract. It does not prove the world state the objective needs.
Use a separate condition at each acceptance boundary:
CharacterSpawnedbefore the first gameplay command;- actor-in-trigger or actor-distance checks for route gates;
- a retained role/workspot completion observer only after its exact semantics have been tested;
- scene named outputs for performance completion; and
- an outer trigger/distance plus durable progression before deactivation.
questCharacterWorkspot_ConditionType exposes puppetRef, isPlayer,
spotRef, animationName, and waitForAnimEnd. Observed in vanilla: four
instances occur in
base\open_world\street_stories\watson\little_china\sts_wat_lch_01\phase\sts_wat_lch_01_combat.questphase.
New completion gates are Experimental; test an already-true condition,
normal completion, interruption, and reload.
Handoff and cleanup are part of the role
Write the owner timeline before connecting the graph:
ambient community spot
-> gameplay AI + temporary role
-> route gate(s)
-> next owner ready
-> clear or replace role
-> scene / defend / new community phase
-> durable outcome
-> safe outer cleanup boundary
-> Deactivate
At a handoff:
- observe that the actor reached the required boundary;
- prepare the next owner, including scene/workspot readiness;
- write any state needed to resume after reload;
- clear or replace the old role only when the next owner can take over;
- keep the community active until every scene, AI, combat, and workspot owner has released the actor; and
- deactivate only on the quest’s explicit cleanup branch.
Clear the role only after the next owner is ready. Otherwise the actor may return to its community spot instead of staying for the next activity.
Manual WolvenKit composition order
Author one behavior delta at a time in a mod-owned questphase:
- In a separate comparison project, extract the smallest cited vanilla questphase and inspect only the focused node. Record its exact class, handle nesting, entity reference, typed params, sockets, and path/workspot references. Do not redistribute the extracted phase.
- In the mod project, confirm the actor’s community activation and
CharacterSpawnedgate already work on a clean save. - Add a
questPuppetAIManagerNodeDefinition. Add one entry, point itsentityReferenceat the exact actor/community entry, and selectaiTier: Gameplayfor the reduced case. - Add a
questMiscAICommandNode. For follower or patrol assignment, set its concreteparamshandle toAIAssignRoleCommandParams, add the intended role subtype, and populate only the focused fields. Record the separatefunctionvalue rather than inferring it from the subtype. - For follower behavior, set
followerRefto the intended target. For patrol, set the exact path NodeRef and explicitly review every path parameter. - If the activity is a direct workspot instead, add
questUseWorkspotNodeDefinition, populateentityReference, addquestUseWorkspotParamsV1, and review all movement, teleport, infinite, interruption, combat, and output choices. - Add separate actor-state conditions for route or activity acceptance. Keep any unproved role-finished/workspot condition Experimental.
- Add the next-owner handoff and a separate role-clear node using
AIClearRoleCommandParamswhere the design requires it. - Route every cut/interruption edge to a branch that releases ownership or
deliberately preserves the actor. Never leave
CutDestinationsemantics implicit. - Save, close, reopen, and inspect the exact typed handles and sockets. Pack the archive, check its depot paths, and confirm no extracted vanilla resource entered the package.
WolvenKit serialization and archive packing are Structurally validated checks. Only the installed build and clean-save observations can become Runtime-proven.
Clean-save acceptance matrix
| Start | Action | Required observation |
|---|---|---|
| Clean pre-activation save | Activate, wait readiness, apply Gameplay tier | One intended actor is ready before the behavior command; no duplicate or early command |
| Same baseline clone | Assign follower role in ordinary walkable space | Actor follows the intended target and actor-position gates, not player-position gates, open in order |
| Same baseline clone | Run the patrol/workspot route | Exact path/spot resolves; movement and animation match every authored parameter |
| Role active | Enter and leave combat or another intended interruption | Authored response occurs; command does not loop, vanish, or deadlock unexpectedly |
| Role active at a supported save point | Save/reload, then continue | One actor and one coherent owner resume; document whether the command must be reissued |
| Actor near a streaming boundary | Stream out and return | Community, path/workspot, tier, and role recover according to the authored policy |
| Final route gate | Transfer to scene, defend, next phase, or cleanup | Old role is retained until the next owner is ready, then cleared exactly once |
| Completed save | Reload and revisit | No stale follower, restarted patrol, duplicate workspot, or reactivated community |
Run every distinct role subtype and every different interruption policy as a separate evidence case. Record archive filename, starting save, actor record and entry, reference and names array, AI tier, concrete params/role type, path or workspot, route, combat state, and the first/last visible behavior.
Common failures
| Symptom | Inspect first |
|---|---|
| Command fires but actor does not move | Spawn readiness, Gameplay tier, command target reference/names, concrete params type, navmesh, and streamed path/workspot |
| Wrong member of a community receives the role | gameEntityReference.reference, names[], entry spelling, and intended multiplicity |
| Actor follows briefly, then returns to the spawn spot | Role cleared or replaced before the next owner; community ambient spot remained active |
| Patrol starts at an unexpected point | Full path NodeRef, continuation policy, startFromClosestPoint, point ordering, and actor spawn position |
| Patrol never completes | isInfinite, loop/back-and-forth policy, path validity, and unproved completion observer |
| Actor teleports into or clips through the workspot | teleport, movement type, spot transform, workspot resource, clearance, and frame/animation compatibility |
| Actor restarts the activity after combat | repeatCommandOnInterrupt, combat policy, durable quest state, and duplicate command path |
| Scene cannot acquire or animate the actor | Active AI role/workspot ownership, scene community reference/entry, actor readiness, and missing handoff |
| Reload produces a different result | Dirty save, saved community/actor state, command reissue policy, stale role, and installed archive filename |
| Node caption says clear while payload assigns | Inspect params.Data concrete type and role handle; do not diagnose from function or caption alone |
Return to Communities and characters.
Scenes
A native scene is not a self-contained quest. It owns performers, screenplay items, timed events, scene-graph flow, entry points, and named exits. Other resources must make its actors available, start it, consume its outcome, and clean up durable world and quest state.
world/community .scene .questphase
activate + spawn actor -> acquire + perform + named exit -> outcome + cleanup
| |
| +-> embedded choice locStore
v
subtitle map/entries + VO map/WEM
That ownership boundary is the central rule of this section:
| Owner | Responsibility in the First Contact example |
|---|---|
| Streaming sector and community | Register #cqa005_com_contact, materialize the contact, and provide the scene marker #cqa005_sm_contact |
.scene / scnSceneResource | Acquire actor 0 and V as actor 1, play one timed line, run a small PuppetAI branch, and expose contact_done |
.questphase / questSceneNodeDefinition | Resolve the soft scene path at the marker, enter through start, and receive contact_done |
| Journal and facts | Advance the meet/leave presentation after the scene returns, then record terminal completion after child cleanup |
| ArchiveXL localization | Merge the external subtitle and voiceover resources used by the spoken-line RUID |
| Save | Retain active quest, journal, fact, checkpoint, community, and scene state independently of a later archive edit |
Reading route
Read these pages in order:
- Scene resource anatomy — the v5 root, typed empty stores, resource references, debug data, and interruption scaffold.
- Actors and performers — community acquisition, player context lookup, performer symbols, actor behavior, and lipsync slot cardinality.
- Screenplay, sections, and events — the separate line, event, section, duration, and ID domains.
- Entry, exit, and quest handoff — the exact
four-node topology and the
questSceneNodeDefinitioninterface. - Author one spoken line — a bounded WolvenKit procedure
for
All clear. Keep moving.and its external subtitle/VO join. - Cleanup and save state — interruption versus completion, named-outcome handling, community teardown, and clean-save acceptance.
- Choices, outcomes, and scene-local localization — native choice items, padded socket observations, embedded locStore joins, and multi-exit quest handoff.
- External VO, WEM, and lipsync — the parallel subtitle, audio, and actor-slot chains plus cardinality-first diagnosis.
- Animation events and scene workspots — animation-set visibility, timed cinematic events, scene workspot identity, and cleanup boundaries.
- Lab 5: First Contact — the complete resource inventory, ownership model, exact graphs, and evidence boundary.
- Author First Contact in WolvenKit — the field-level construction and round-trip procedure.
- Test First Contact — clean-save playback, reload, streaming, handoff, and cleanup checks.
First Contact uses one spoken line, an empty embedded locStore, and no
cinematic-animation or scene-workspot payload. Continue to the advanced pages
when adding choices, animation, or workspots.
Vanilla references
Use WolvenKit to extract these resources from your own installed archives and inspect only the fields needed for comparison:
base\quest\minor_quests\mq003\scenes\mq003_01_homeless.scenebase\quest\minor_quests\mq003\scenes\mq003_03_orbital_pod.scenebase\quest\minor_quests\mq007\scenes\mq007_01_gun_found.scenebase\quest\minor_quests\mq010\scenes\mq010_02_barry_talk.scenebase\animations\quest\minor_quests\mq007\anim\body\mq007__talking_gun__male_fpp.animsbase\workspots\common\wall\generic__stand_wall_lean_left__stand_around__01.workspotbase\localization\en-us\lipsync\base\quest\minor_quests\mq007\scenes\mq007_01_gun_found\skippy.animsbase\localization\en-us\lipsync\base\quest\minor_quests\mq007\scenes\mq007_01_gun_found\v.anims
These paths are citations, not redistributable examples. Do not publish the extracted CR2W files or complete serialized exports. Record focused types, properties, IDs, and edges instead; the workflow is described in Inspect a vanilla questphase.
First-pass failure routing
| Symptom | Inspect first |
|---|---|
| Scene never begins | Community Activate, CharacterSpawned, world marker resolution, phase prefab scope, soft scene path, and start socket |
| Crash or failure during scene startup | Actor acquisition, readiness, lipsync ID-to-array cardinality, and missing referenced resources |
| Subtitle is absent | Scene line RUID, registered subtitle map, its subtitle-entry reference, and matching stringId |
| Subtitle works but audio is silent | VO-map registration, matching stringId, both gender paths, WEM depot path, and audio logs |
| Choice labels are blank or stale | Screenplay option locstring, unsigned locale-block ordering, descriptor/payload variantId, and vpeIndex |
| Choice takes the wrong branch | Option order, output ordinal, destination, and stale active-scene state |
| Line plays but the quest does not continue | Scene exit name and node, scene-node output socket name, and outgoing questphase connection |
| Animation does not play or deforms the actor | Performer mapping, actor-visible animation-set slot, animation name, component/mask, and rig compatibility |
| Scene workspot never succeeds | Actor reference, workspot data/instance IDs, transform, params, wrapper mapping, and output semantics |
| Contact remains after completion | Leave condition, community Deactivate, and any durable community/device state; reaching scnEndNode is not cleanup |
| A rebuild behaves like an older graph | Save history, active scene/checkpoint state, facts, and journal state before editing more resources |
Next: Scene resource anatomy.
Scene resource anatomy
The First Contact scene is a native .scene CR2W resource rooted at
scnSceneResource. Its root records more than the visible node graph. Actor
acquisition, entry and exit names, screenplay data, localization storage,
referenced animation sets, interruption policy, debug symbols, and graph
metadata all belong beside the graph.
The outer resource
This is the focused shape used by the completed checkpoint. Handle wrappers and unrelated serialization metadata are omitted:
scnSceneResource
├── version: 5
├── cookingPlatform: PLATFORM_PC
├── sceneCategoryTag: minorQuests
├── actors: [contact actor 0]
├── playerActors: [V actor 1]
├── entryPoints: [start -> node 1]
├── exitPoints: [contact_done -> node 3]
├── interruptionScenarios: [Default scenario 0]
├── screenplayStore: scnscreenplayStore
├── locStore: scnlocLocStoreEmbedded
├── resouresReferences: scnSRRefCollection
├── sceneGraph: scnSceneGraph
├── sceneSolutionHash: scnSceneSolutionHash
└── debugSymbols: scnDebugSymbols
version: 5 is the scene-root version. It is not the outer WolvenKit JSON
format version or the CR2W header’s game-version number. Likewise,
PLATFORM_PC is the cooking target and minorQuests is the category tag; they
do not register or launch the resource.
The serialized property is spelled exactly resouresReferences. The missing
second c is part of the native schema name. Correcting it to natural English
creates a different, unrecognized property in a hand-edited source.
Typed stores and collections
Empty typed containers are still part of a sound minimal resource. This focused WolvenKit-shaped excerpt shows the decisive types:
{
"locStore": {
"$type": "scnlocLocStoreEmbedded",
"vdEntries": [],
"vpEntries": []
},
"resouresReferences": {
"$type": "scnSRRefCollection",
"lipsyncAnimSets": [
{
"$type": "scnLipsyncAnimSetSRRef",
"asyncRefLipsyncAnimSet": {
"DepotPath": "base\\animations\\facial\\generic\\interactive_scene\\generic_facial_lipsync_gestures.anims"
}
}
],
"cinematicAnimNames": [],
"cinematicAnimSets": [],
"dynamicAnimNames": [],
"dynamicAnimSets": [],
"gameplayAnimNames": [],
"gameplayAnimSets": [],
"ridAnimations": []
}
}
The full scnSRRefCollection in this checkpoint also retains typed empty
arrays for RID animation containers, animation sets, camera animations,
cyberware animation sets, deformation animation sets, and facial animation
sets. The root keeps empty ridResources, effects, props, markers, notable
points, reference points, VO-info, and workspot arrays. These values describe
the intentionally small scope; they are not invitations to delete the owning
properties indiscriminately.
The spoken line is externally localized, so scnlocLocStoreEmbedded remains
empty. That store becomes relevant for scene choices, not for the subtitle and
voice path taught here. See Localization paths.
Solution and debug data
The checkpoint retains the nested solution-hash types:
sceneSolutionHash: scnSceneSolutionHash
└── sceneSolutionHash: scnSceneSolutionHashHash
└── sceneSolutionHashDate: <unsigned value>
Treat sceneSolutionHashDate as scene metadata that must survive the authoring
and round-trip workflow. Do not use it as a screenplay, event, or content
identity, and do not claim that matching it proves runtime behavior.
scnDebugSymbols is also structured data. First Contact supplies two
performersDebugSymbols rows and explicit empty arrays for:
sceneEventsDebugSymbols;sceneNodesDebugSymbols;workspotsDebugSymbols.
Debug rows do not acquire actors or connect graph nodes. They map editor-facing performer information onto the native scene identities explained in Actors and performers.
Default interruption scenario
First Contact includes one scnInterruptionScenario, not an omitted or null
policy:
| Property | Exact checkpoint value |
|---|---|
id | scnInterruptionScenarioId 0 |
name | Default |
enabled | 1 |
| Interrupt condition | scnCheckSpeakersDistanceInterruptCondition; comparison Greater; distance 6 |
| Return condition | scnCheckSpeakersDistanceReturnCondition; comparison Less; distance 5 |
playInterruptLine | 1 |
talkOnReturn | 1 |
forcePlayReturnLine | 0 |
playingLinesBehavior | Default |
The unequal thresholds create a one-unit hysteresis band: the distance must
rise above 6 to interrupt and fall below 5 to return. Do not rewrite them as a
single equality test. The scenario’s interrupt/return line policy is scene
behavior; it does not set a completion fact, choose the contact_done exit, or
deactivate a community. Those are separate handoff and cleanup operations.
The scenario shape and distance-condition families are Observed in
vanilla. The exact First Contact serialization is Structurally validated.
The supplied Lab 5 tests do not interrupt an active line or exercise
CutDestination. If your scene supports those paths, add explicit walk-out,
return, and reload cases rather than inferring their behavior from normal
playback.
What to preserve in a focused comparison
When comparing a WolvenKit round trip, review semantic structure rather than incidental handle allocation:
- confirm the root type,
version, platform, and category; - confirm every typed store and collection still has its native
$type; - compare actor, performer, graph-node, entry, exit, screenplay, event, and locstring IDs in their own domains;
- compare the lipsync array length with every actor’s referenced slot;
- compare both interruption conditions and their thresholds;
- compare graph edges, output and input stamps,
startNodes, andendNodes; - confirm the solution-hash and debug-symbol containers remain present.
A successful comparison is Structurally validated evidence. It does not show that the marker resolves, the contact is ready, subtitles merge, audio plays, or the exit returns to the questphase.
Previous: Scenes. Next: Actors and performers.
Actors and performers
Actor IDs, performer IDs, entity references, and lipsync slots are separate domains. First Contact deliberately uses small values that make those joins easy to audit, but their proximity does not make them interchangeable.
Contact: actor 0 from a community
The non-player speaker is scnActorDef actor 0. Its selected acquisition
plan is community:
{
"$type": "scnActorDef",
"actorId": { "$type": "scnActorId", "id": 0 },
"actorName": "contact",
"acquisitionPlan": "community",
"communityParams": {
"$type": "scnCommunityParams",
"reference": "#cqa005_com_contact",
"entryName": "contact",
"forceMaxVisibility": 0
},
"lipsyncAnimSet": {
"$type": "scnLipsyncAnimSetSRRefId",
"id": 0
}
}
The excerpt shortens the typed NodeRef and CName wrappers for readability.
The full actor definition also retains the typed parameter blocks and empty
animation-set arrays expected by the resource. Do not assume that deleting
inactive acquisition parameter blocks is equivalent to leaving their native
defaults present.
acquisitionPlan: community does not activate a community or wait for its
entry to exist. Before the questphase starts the scene, it must:
- activate the community at
#cqa005_com_contact; - wait on
CharacterSpawnedfor the relevant entry or whole-community scope; - complete any broad setup needed for visibility and workspot state;
- only then enter the scene through
start.
That sequence is the bridge between Communities and characters and this section. Starting the scene while the actor is still materializing can turn an otherwise valid scene into a timing-dependent failure.
V: actor 1 from scene context
The player is a separate scnPlayerActorDef, actor 1. Its selected plan is
findInContext, and the context lookup is constrained by the player record:
{
"$type": "scnPlayerActorDef",
"actorId": { "$type": "scnActorId", "id": 1 },
"acquisitionPlan": "findInContext",
"playerName": "V",
"findActorInContextParams": {
"$type": "scnFindEntityInContextParams",
"contextualName": "Player",
"specRecordId": "Character.Player_Puppet_Base"
},
"specCharacterRecordId": "Character.Player_Puppet_Base",
"lipsyncAnimSet": {
"$type": "scnLipsyncAnimSetSRRefId",
"id": 0
}
}
The two shown record values are serialized TweakDBID fields in the native
resource, not strings in the final CR2W type system. V is not supplied by the
contact community and should not be given the contact’s NodeRef or entry name.
Performer debug symbols
First Contact’s two scnPerformerSymbol rows use performer IDs 1 and 257:
| Scene role | Actor ID | Performer ID | Debug entity reference |
|---|---|---|---|
| Contact | 0 | 1 | #cqa005_com_contact, name contact |
| V | 1 | 257 | #player |
This exact fixture follows the observed actor ID * 256 + 1 performer-symbol
convention. Treat that as an authoring relationship to preserve in this shape,
not permission to substitute performer 257 anywhere a scnActorId(1) is
required. The screenplay line, section actor behavior, community acquisition,
and debug symbol each expect their own typed identity.
One addressable lipsync slot
Both actor definitions reference scnLipsyncAnimSetSRRefId 0. The scene has
exactly one row, at array slot 0, under the natively spelled
resouresReferences.lipsyncAnimSets:
slot 0
└── base\animations\facial\generic\interactive_scene\
generic_facial_lipsync_gestures.anims
The lipsync ID is an array index, not an actor ID or performer ID. Therefore:
- actor
0-> lipsync slot0is addressable; - actor
1-> lipsync slot0is addressable; - actor
1-> lipsync slot1would be invalid when the array has one row.
For two performers, check both slot IDs against the cooked resource table. Sharing one row can help isolate an invalid index, but still requires a visual check of both performers.
Use distinct NPC and player lipsync resources later only when every referenced slot remains separately addressable after cooking and is runtime-tested. Facial-animation quality is outside the lab’s normal playback checks and must be reviewed visually for the final actor and audio assets.
Section actor behavior
The completed scene’s scnSectionNode contains one
scnSectionInternalsActorBehavior per actor:
| Actor | behaviorMode |
|---|---|
0 contact | OnlyIfAlive |
1 V | OnlyIfAlive |
This bounds section participation when an actor is not alive; it is not an
actor-readiness wait and it cannot replace CharacterSpawned. Nor does it
define how a dead or missing contact should change the surrounding quest.
Alternative/failure quest routing remains a separate design problem.
Screenplay, sections, and events
One spoken line spans three native objects. The screenplay item says who speaks and which localization ID to resolve. A timed event schedules that item. A section owns the event and determines when graph flow can leave.
screenplayStore.lines[0]
scnscreenplayDialogLine item 1
^
| screenplayLineId 1
scnDialogLineEvent, start 0, duration 2598 ms
|
v
scnSectionNode 2, sectionDuration 2998 ms
Removing any layer produces a different resource: a screenplay row alone is not scheduled, an event with an unresolved item ID has no line definition, and a section duration that ends too early can cut off its event.
The screenplay line
First Contact’s scnscreenplayStore.lines contains exactly one
scnscreenplayDialogLine; its options array is empty:
{
"$type": "scnscreenplayDialogLine",
"itemId": { "$type": "scnscreenplayItemId", "id": 1 },
"speaker": { "$type": "scnActorId", "id": 0 },
"addressee": { "$type": "scnActorId", "id": 1 },
"locstringId": {
"$type": "scnlocLocstringId",
"ruid": "9638591835734011695"
},
"usage": {
"$type": "scnscreenplayLineUsage",
"playerGenderMask": { "$type": "scnGenderMask", "mask": 3 }
}
}
Actor 0 is the contact and actor 1 is V. Gender mask 3 is the exact
line-usage value in this checkpoint. It sits beside male and female lipsync
animation names set to None; it does not select the subtitle resource or
replace the actor definitions.
The line stores no English sentence. Its RUID
9638591835734011695 resolves to All clear. Keep moving. through external
subtitle and VO resources. Consequently the scene’s
scnlocLocStoreEmbedded can remain typed and empty. Embedded locStore payloads
are for scene-owned localized data such as choice labels, not this line’s
external spoken-text path.
For custom clips, those two animation-name fields need their own lookup check:
confirm the selected CName exists in the intended animset. See
Custom clip checks
before replacing None; a valid RUID or WEM does not validate an animation.
The timed line event
Section node 2 contains exactly one handled scnDialogLineEvent:
| Property | Exact value |
|---|---|
screenplayLineId | scnscreenplayItemId 1 |
id | scnSceneEventId 8646165628675208917 |
startTime | 0 |
duration | 2598 ms |
visualStyle | regular |
voContext | Vo_Context_Quest |
voExpression | Vo_Expression_Spoken |
additionalSpeakers.speakers | [] |
The event points to screenplay item 1; it does not point directly to the
locstring. Event ID 8646165628675208917 and locstring RUID
9638591835734011695 are intentionally different unsigned identifiers. Keep
both as unsigned decimal values through tooling and comparison. Do not derive
one from the other, reuse a graph node ID, or truncate either to 32 bits.
The event duration is aligned to the supplied audio’s measured duration for this fixture. It is not inferred from subtitle character count.
The section
scnSectionNode 2 owns the event and two output sockets:
Section 2
├── actorBehaviors
│ ├── actor 0: OnlyIfAlive
│ └── actor 1: OnlyIfAlive
├── events
│ └── DialogLineEvent: item 1, 2598 ms
├── sectionDuration: scnSceneTime(stu = 2998)
├── output stamp name 0 / ordinal 0 -> End 3 input 0 / 0
└── output stamp name 1 / ordinal 0 -> no destination
For First Contact, the section-duration contract is:
2598 ms line duration + 400 ms tail = 2998 ms section duration
The 400 ms tail is a deliberate lab value that lets the line finish before the section reaches End. It is not claimed as a universal engine constant. For multiple or overlapping events, calculate the latest event end and add an appropriate tested tail instead of adding 400 ms blindly to every line.
Output stamp 0/0 is the normal completed route. Output stamp 1/0 is an
explicit cancel-shaped socket with no destination in this minimal scene. A
named quest exit is attached to End node 3, not to the screenplay line or
event itself.
Keep the ID domains separate
| Typed domain | First Contact value | Identifies |
|---|---|---|
scnActorId | 0, 1 | Contact and V |
scnPerformerId | 1, 257 | Debug performer symbols |
scnNodeId | 1, 2, 3, 4 | Start, Section, End, PuppetAI wrapper |
scnscreenplayItemId | 1 | The line definition and event-to-line join |
scnSceneEventId | 8646165628675208917 | The timed event instance |
scnlocLocstringId | 9638591835734011695 | External subtitle and voice lookup |
scnLipsyncAnimSetSRRefId | 0 | Index into the lipsync resource-reference array |
The repeated integer 1 across several rows is coincidence plus a deliberately
small fixture. See Identifier domains
before comparing raw exports.
Entry, exit, and quest handoff
The scene graph and the questphase graph meet through names, but they remain
different graph systems. First Contact exposes scene entry start, exposes
named scene exit contact_done, and gives a questSceneNodeDefinition sockets
with exactly those names.
Canonical four-node scene
The completed scene has four scene-node IDs:
+-> Section 2 -> End 3 [contact_done]
Start 1 -- one fan-out --+
+-> scnQuestNode 4 / PuppetAI [fire-and-forget]
Start uses one output socket with two destinations. The section controls the line’s duration and reaches End. The PuppetAI branch has no output destination, so it is deliberately fire-and-forget and does not join the line branch before the named exit.
The exact semantic edges and stamps are:
| Source node | Source output stamp | Destination | Destination input stamp |
|---|---|---|---|
Start 1 | name 0, ordinal 0 | Section 2 | name 0, ordinal 0 |
Start 1 | name 0, ordinal 0 | PuppetAI wrapper 4 | name 0, ordinal 1 |
Section 2 normal | name 0, ordinal 0 | End 3 | name 0, ordinal 0 |
Section 2 cancel | name 1, ordinal 0 | no destination | — |
PuppetAI wrapper 4 | name 0, ordinal 0 | no destination | — |
End 3 | no output sockets | — | — |
Do not turn the two Start destinations into two differently stamped outputs.
The fixture has one 0/0 fan-out. Likewise, the PuppetAI destination’s
0/1 is an input stamp, not the wrapper’s output stamp.
The graph declares its boundary nodes independently of their appearance in
the graph array:
startNodes: [scnNodeId(1)]
endNodes: [scnNodeId(3)]
entryPoints:
start -> scnNodeId(1)
exitPoints:
contact_done -> scnNodeId(3)
startNodes/endNodes identify the graph’s boundary nodes. entryPoints and
exitPoints publish named routes for callers. Preserve both layers; merely
naming an exit does not add End to endNodes, and merely listing End does not
publish contact_done.
The scene-local PuppetAI node
Scene node 4 is an scnQuestNode wrapper around a
questPuppetAIManagerNodeDefinition, also with graph-local ID 4 in this
focused fixture. Its internal quest node has:
| Property | Value |
|---|---|
| Entry target | Contact entity reference beneath #cqa005_com_contact |
aiTier | Cinematic |
| Quest sockets | CutDestination, In, Out |
Wrapper isockMappings | CutDestination, In |
Wrapper osockMappings | Out |
| Wrapper output destinations | none |
The wrapper’s input ordinal 1 maps Start’s destination to internal socket
In; ordinal 0 is the CutDestination mapping. This is why changing the
destination stamp from 0/1 to 0/0 is a semantic edit, not cosmetic graph
layout.
The branch requests the contact’s Cinematic AI tier alongside the spoken
section. It neither ends the scene nor emits a completion signal. The section
branch alone reaches End 3. Since no join waits for an AI output, treat this
arrangement as fire-and-forget and test teardown separately.
Questphase scene node contract
The child questphase launches the archived scene through one
questSceneNodeDefinition. Its interface is exact:
| Socket | Socket type | First Contact connection |
|---|---|---|
CutDestination | CutDestination | Unconnected |
start | Input | Connected from the child flow after actor readiness/setup |
contact_done | Output | Connected to success-state and cleanup progression |
Default INT | Output | Unconnected |
Default RET | Output | Unconnected |
There is no output socket named end. The successful join is the exact
scene-exit name contact_done. Adding a questphase end output does not make
it match the scene’s End node.
The decisive non-socket properties are:
questSceneNodeDefinition
├── interruptionOperations: []
├── notAllowedToBeFrozen: 0
├── reapplyInterruptionOperationsAfterGameLoad: 0
├── syncToMusic: 0
├── sceneFile:
│ ├── DepotPath: mod\cqa\cqa005\scenes\cqa005_first_contact.scene
│ └── Flags: Soft
└── sceneLocation: scnWorldMarker
└── nodeRef: #cqa005_sm_contact
The soft sceneFile is a depot reference to a .scene carried in the packed
archive. It is not an ArchiveXL root registration. ArchiveXL registers the
quest root and localization branches in this design; the active questphase
resolves the soft scene resource when execution reaches this node.
sceneLocation is a typed scnWorldMarker. Its local NodeRef must resolve
beneath the prefab scope available to the active phase composition. A valid
scene path cannot supply a missing marker, and a registered marker cannot
supply a missing scene. See Quest prefabs and
NodeRefs.
Empty interruptionOperations and unconnected default interruption/return
outputs are exact boundaries for this small lab. They do not prove that a
larger quest can omit interruption handling, nor that CutDestination has
been runtime-validated.
Start checkpoint boundary
The downloadable start checkpoint keeps a safe scene shell with only:
Start 1 (output 0/0) -> End 3 (input 0/0)
It still declares startNodes[1], endNodes[3], entry start, and exit
contact_done. The root and child start quest graphs must not invoke that
scene node. The shell exists so the learner edits a native resource with its
actor/debug/localization/reference scaffold intact; it is not a playable
pre-completion shortcut.
Author one spoken line
Turn the First Contact start scene’s Start-to-End shell into one community-acquired spoken line with one named exit in WolvenKit.
Procedure boundary
| Record | Exact value |
|---|---|
| Review date | 2026-08-09 |
| Game | Cyberpunk 2077 Windows GOG 2.31a |
| WolvenKit | 8.19.0 |
| ArchiveXL | 1.27.0 |
| RED4ext | 1.30.0 |
| redscript | 0.5.31 |
| Structural status | Structurally validated |
| In-game verification | Follow the Lab 5 test procedure for playback, handoff, reload, and cleanup |
Clean-save requirement: authoring and structural checks can use any disposable project copy, but runtime acceptance establishes two untouched manual originals that have never loaded any CQA Lab 1–5 candidate. Case 1 creates the exact pre-scene, post-contact, and completed manual seeds; later cases use closed-game full-slot clones. Labs 3 and 4 share the Lab 5 site. Active quest nodes, checkpoints, journal state, facts, community state, scene state, triggers, and streamed-world state can survive archive removal or replacement. A save made after any tutorial checkpoint or failed probe is not an eligible original.
Work in a copy of the Lab 5 start project. Keep the completed checkpoint beside it as executable reference material, but enter and inspect every supplied node and property rather than treating it as a magic template.
1. Audit the inert start scene
Open
mod\cqa\cqa005\scenes\cqa005_first_contact.scene in WolvenKit. Before
adding dialogue, verify this exact shell:
| Area | Start checkpoint contract |
|---|---|
| Root | scnSceneResource, version: 5, PLATFORM_PC, minorQuests |
| Actors | Contact actor 0 from community entry contact; V actor 1 by findInContext |
| Debug symbols | Performer 1 for the contact and 257 for V; other debug arrays explicitly empty |
| Lipsync | Both actors reference slot 0; one generic lipsync row exists |
| Public routes | Entry start -> node 1; exit contact_done -> node 3 |
| Graph | Start 1 output 0/0 -> End 3 input 0/0; startNodes[1]; endNodes[3] |
| Screenplay | Typed scnscreenplayStore; lines: []; options: [] |
| Embedded localization | Typed scnlocLocStoreEmbedded; vdEntries: []; vpEntries: [] |
The start root and child must not invoke the scene shell. Otherwise the meeting can complete through Start-to-End without playing the line.
2. Add the screenplay item
Under screenplayStore.lines, add one scnscreenplayDialogLine with:
| Property | Value |
|---|---|
itemId | scnscreenplayItemId(1) |
speaker | scnActorId(0) |
addressee | scnActorId(1) |
locstringId.ruid | unsigned 9638591835734011695 |
usage.playerGenderMask.mask | 3 |
| Male/female lipsync animation name | None |
Do not paste All clear. Keep moving. into the scene. The RUID owns the join;
the English text lives in the external subtitle-entry resource.
3. Replace the direct edge with a timed section
Keep Start ID 1 and End ID 3. Add Section ID 2, containing:
- actor
0behaviorOnlyIfAlive; - actor
1behaviorOnlyIfAlive; - one handled
scnDialogLineEventatstartTime: 0; screenplayLineId: scnscreenplayItemId(1);- unsigned event ID
8646165628675208917; - event duration
2598ms; sectionDuration: scnSceneTime(stu = 2998);- normal output stamp
0/0to End3input0/0; - cancel output stamp
1/0with no destination.
The section is 400 ms longer than the line in this fixture. Preserve that explicit tail; do not let End become reachable at 2598 ms.
4. Add the parallel PuppetAI branch
Add scene node 4 as an scnQuestNode wrapping a
questPuppetAIManagerNodeDefinition. Give the inner node CutDestination,
In, and Out sockets, map them through the wrapper, target the contact entry,
and set aiTier: Cinematic.
Rebuild Start 1 as one output socket stamped 0/0 with two destinations:
destination Section 2: input stamp name 0 / ordinal 0
destination PuppetAI 4: input stamp name 0 / ordinal 1
The PuppetAI wrapper retains one output stamped 0/0 with no destination. It
is fire-and-forget. Do not connect it to End and do not add a join. Keep
startNodes[1], endNodes[3], entry start -> 1, and exit
contact_done -> 3 unchanged.
5. Verify actor and resource joins
Confirm actor 0 still selects community acquisition from
#cqa005_com_contact, entry contact, while actor 1 still selects
findInContext with Character.Player_Puppet_Base. Confirm performer IDs
remain 1 and 257.
Both actors must reference lipsync slot 0, and slot 0 must remain backed by:
base\animations\facial\generic\interactive_scene\
generic_facial_lipsync_gestures.anims
The native collection property is resouresReferences. Do not add a duplicate
row merely because two actors share it, and do not change V to slot 1 while
the array still contains one row.
6. Complete the external localization and audio path
Use the same unsigned RUID in all three joins:
scene line: scnlocLocstringId.ruid = 9638591835734011695
|
+-> registered subtitle map
| -> cqa005_subtitles.json
| -> localizationPersistenceSubtitleEntry.stringId
| -> "All clear. Keep moving."
|
+-> registered VO map
-> locVoLineEntry.stringId
-> femaleResPath / maleResPath
-> contact_i_85c3283507e7ef2f.wem
The subtitle branch needs two resources: a
localizationPersistenceSubtitleMap registered under
localization.subtitles.en-us, and the
localizationPersistenceSubtitleEntries resource referenced by that map. The
VO branch needs a locVoiceoverMap registered under
localization.vomaps.en-us; both gender paths point to the supplied WEM in
this focused fixture.
| Role | Cooked depot path |
|---|---|
| Subtitle map | mod\cqa\cqa005\localization\en-us\subtitles\cqa005_subtitles_map.json |
| Subtitle entries | mod\cqa\cqa005\localization\en-us\subtitles\cqa005_subtitles.json |
| VO map | mod\cqa\cqa005\localization\en-us\vo\cqa005_vo.json |
| Voice asset | mod\cqa\cqa005\localization\en-us\vo\contact_i_85c3283507e7ef2f.wem |
The supplied WAV and WEM are synthetic lab assets. Keep their license with redistributed copies. Audio conversion is covered in External VO, WEM, and lipsync.
For the full field-level localization chain, see Localization paths.
7. Connect the questphase only in the completed project
In the completed child phase, reach the scene only after community activation,
CharacterSpawned, and broad setup. The questSceneNodeDefinition must use:
- soft scene path
mod\cqa\cqa005\scenes\cqa005_first_contact.scene; scnWorldMarkerNodeRef#cqa005_sm_contact;- input
start; - output
contact_done; - unconnected
CutDestination,Default INT, andDefault RETroutes; interruptionOperations: [];notAllowedToBeFrozen: 0;reapplyInterruptionOperationsAfterGameLoad: 0;syncToMusic: 0.
There is no end output. Connect contact_done to the quest’s success and
post-scene flow. Keep the start root and start child unable to reach the scene.
8. Round-trip and inspect
Use WolvenKit 8.19.0 to convert/serialize the edited resource, deserialize it
again, and compare the focused contract:
- root type, v5/platform/category, typed empty containers, and exact native property names;
- actor acquisition and performer-symbol joins;
- lipsync reference array length and every slot index;
- screenplay item
1, event-to-item join, distinct unsigned event/RUID values, and2598/2998timing; - graph IDs, all five output-socket states, all three connected edges, and both destination input stamps;
startNodes[1],endNodes[3], and the named entry/exit;- questphase socket names/types, soft depot path, and marker NodeRef;
- subtitle map-to-entries path, VO map-to-WEM paths, and exact matching RUIDs.
Then pack the normal WolvenKit project and inspect ArchiveXL and game logs for missing paths or localization merge errors. A clean round trip and successful pack are Structurally validated evidence only.
9. Runtime acceptance
From the pre-install manual save, verify in one recorded run:
- the community activates and the contact becomes ready before scene launch;
- normal approach does not crash or race actor acquisition;
- the contact says
All clear. Keep moving.once; - the subtitle appears and the synthetic WEM plays;
- the scene reaches
contact_done, not a nonexistentendroute; - the post-scene objective/pin handoff occurs once;
- leaving the cleanup area deactivates the contact, after which the child returns and the root writes its terminal fact;
- the named pre-scene seed loads in Cases 3/4/7 and the post-
contact_doneand completed save/reload cases behave as recorded in the Lab 5 matrix.
The supplied Lab 5 cases cover the ordinary cqa005 route. Facial and
animation quality, active-line interruption or CutDestination
behavior, arbitrary/unlisted pre-scene active-child states, and
active-line/interruption reload remain Experimental independently of that
marker.
Common mismatches
| Symptom | Likely first mismatch |
|---|---|
| Line has no text | RUID differs between scene and subtitle entry, subtitle map is not registered, or the map points to the wrong entries resource |
| Subtitle appears but audio is silent | VO-map registration, VO stringId, gender WEM path, missing WEM, or incompatible audio asset |
| Crash near scene launch | Actor not ready or a lipsync index is not addressable after cooking |
| Scene exits immediately | Root/child start graph invoked the shell, or Section is bypassed by a retained Start-to-End edge |
| Scene never completes | Section normal output misses End, End is absent from endNodes, or the quest expects end instead of contact_done |
| Line is clipped | Event duration and section duration do not preserve the measured line plus tail |
Previous: Entry, exit, and quest handoff. Next: Cleanup and save state.
Cleanup and save state
Scene termination is not quest completion, and quest completion is not world cleanup. First Contact makes every boundary visible so one successful spoken line cannot hide a leaked actor, stale marker, or save-backed old graph.
Five different completion boundaries
| Boundary | Native owner | What it establishes | What it does not establish |
|---|---|---|---|
| Section normal output | .scene graph | The timed line section reached its normal socket | A public named outcome or quest transition |
End node 3 | .scene graph | The scene reached a terminating graph node | Community deactivation or a persistent completion fact |
Exit contact_done | .scene + questSceneNodeDefinition interface | The child phase can continue through the matching output | Cleanup has occurred |
| Child phase output | Child .questphase | Meet/leave objectives and community teardown completed | The root completion guard/fact has been updated |
Fact cqa005_completed | Root .questphase and save | Later starts can take the already-completed route | Journal, scene, or community state was automatically erased |
Do not collapse these into a generic “end.” In particular, there is no
quest-scene-node output called end; the public success name is
contact_done.
Exact completed lifecycle
The completed child phase orders actor readiness, scene handoff, and teardown:
child Input
-> meet objective Active
-> contact mappin Active
-> community Activate
-> CharacterSpawned
-> setup trigger IsInside
-> checkpoint
-> scene start
-> scene contact_done
-> meet objective Succeeded
-> contact mappin Inactive
-> leave objective Active
-> cleanup trigger IsOutside
-> community Deactivate
-> leave objective Succeeded
-> child Output
After the child returns, the root succeeds the quest journal state, writes
cqa005_completed, and exits. On a later activation, its completion guard can
bypass the child rather than spawning the contact again.
The order is intentional:
CharacterSpawnedprevents the scene racing a community entry that has not materialized;contact_doneis consumed before the meet objective and pin change;- the leave objective makes delayed cleanup visible to the player;
IsOutsideavoids deactivating the contact at speaking distance;- deactivation completes before the leave objective and child output;
- the durable completion fact is written only after the child returns.
A failed or interrupted scene must not accidentally reach the same success
chain. Lab 5 exercises the ordinary cqa005 completion through
contact_done. Active-line interruption, CutDestination, and interrupted
recovery require separate tests if your scene uses them.
Scene interruption is not cleanup
The scene’s Default interruption scenario reacts when speaker distance rises
above 6 and permits return after it falls below 5. With
playInterruptLine: 1 and talkOnReturn: 1, it describes how the conversation
may interrupt and resume.
That policy is separate from all of these:
scnEndNode3and named exitcontact_done;- the quest scene node’s unconnected
Default INTandDefault REToutputs; - its empty
interruptionOperations; - its unconnected
CutDestination; - the wider
IsOutsidecleanup condition; - community
Deactivate; - the persistent completion fact.
Do not route a six-unit conversational interruption directly to world cleanup. The player may return within five units and continue. Conversely, reaching End does not remove the community; the questphase retains it until the leave boundary is crossed.
notAllowedToBeFrozen: 0,
reapplyInterruptionOperationsAfterGameLoad: 0, and syncToMusic: 0 are exact
scene-node values in the fixture. They are not proof of save/reload or cut
semantics. Those behaviors require runtime observation.
State that can outlive an archive edit
Treat these as separate save variables while diagnosing the lab:
| Save-backed or persistent area | First Contact example |
|---|---|
| Facts | cqa005_completed and any prior probe values |
| Journal | Quest/objective state, visited state, tracked entry, and mappin state |
| Quest graph | Active root/child nodes and checkpoint progress |
| Scene | Active or interrupted scene state and its return path |
| Community | Activation, spawned entries, phase/workspot state, and deactivation progress |
| Devices | Controller persistent state if a later lab adds a device; no device is required by this one-line scene |
Replacing a .scene, .questphase, or archive does not clear these domains.
Resetting only cqa005_completed is also insufficient: a save can still retain
an active child, journal state, checkpoint, scene, or community from an older
payload. See Facts, journals, and
saves.
Practical cleanup matrix
Run this matrix on Cyberpunk 2077 Windows GOG 2.31a with WolvenKit 8.19.0,
ArchiveXL 1.27.0, RED4ext 1.30.0, and redscript 0.5.31. Record the packed
payload inventory, exact save history, installed framework versions, and result
for every row.
The Lab 5 test procedure covers the supplied checkpoint. Also test active-scene reload and interruption when adding those behaviors.
The frozen campaign begins from two untouched manual-save originals, then Case 1 creates the three named manual seeds under the unchanged candidate. Cases 3/4/7, 5/6/8, and 9/11 load distinct full-slot clones from their respective seed. Close the game before cloning. Never use an autosave, checkpoint save, or a save carried forward from a failed or differently build when comparing the result.
| Case | Save point and action | Required observations |
|---|---|---|
| Normal route | Clean save; approach, hear the line, leave the outer cleanup area | Contact is ready before launch; line occurs once; contact_done advances; pin changes; community deactivates after leaving; objectives and fact finish once |
| Named pre-scene loads (Cases 3/4/7) | Load a distinct byte-identical clone of seed-pre-scene-outside-setup, then follow the exact case route | No duplicate contact or objective transition; actor reacquires; Cases 3/4 start once and Case 7 survives its stream-away/return |
| Arbitrary pre-scene save/load | Save at any unlisted active-child point, then load | Record the exact behavior; do not infer it from the supplied seed-save cases |
| Active-scene reload | Where saving is permitted, save during the line or interruption and reload; otherwise record the nearest game-provided checkpoint | No crash, stale speaker, duplicate line, false success, or leaked Cinematic AI state; exact behavior is recorded rather than inferred |
| Interrupt and return | Cross above 6 units during the line, then return below 5 | Default interruption/return behavior is coherent and does not trigger cleanup or completion accidentally |
| Named post-contact loads (Cases 5/6/8) | Load distinct clones of seed-post-contact-inside-cleanup, then follow the exact case route | Meet and pin state remain correct; replay is suppressed; cleanup/deactivation occurs once |
| Named completed loads (Cases 9/11) | Load distinct clones of seed-completed under each case’s installation condition | Root guard or removal isolation behaves as frozen; final journal state does not reactivate the quest |
| Case 7 stream away and return | Load its seed-pre-scene-outside-setup clone, leave the finite Quest descriptor by ordinary movement, return, and finish | NodeRefs reacquire, active stage remains coherent, and no duplicate community/scene launch occurs |
If a case cannot be executed because the game forbids saving at that moment, record that limitation. Do not convert an untested save point into a passing claim.
Failure routing
| Symptom | Inspect first |
|---|---|
| Contact vanishes during the line | Cleanup branch reached too early, wrong trigger state/NodeRef, or an interruption route was conflated with deactivation |
| Contact remains after leaving | IsOutside condition, community target/scope, Deactivate operation, or active save state from an older graph |
| Meet succeeds but leave objective never appears | Connection order after contact_done, journal socket names, and pin-inactive node |
| Quest finishes but contact remains | Root fact/journal completion bypassed child cleanup, or deactivation occurred after rather than before child output |
| Quest restarts after completion | Completion fact write, root guard comparison/branch, or test save history |
| Behavior changes only after reload | Active quest checkpoint, scene interruption state, community phase, journal state, and installed build |
| Start checkpoint completes the meeting | Root or child start graph invokes the inert Start 1 -> End 3 scene shell; remove that invocation from the start project |
Choices, outcomes, and scene-local localization
A scene choice joins four independent structures: the visible option in the
screenplay store, its embedded localization payload, an option row on a
scnChoiceNode, and one scene-graph output. A quest outcome is a fifth
structure. It becomes durable only when the scene reaches a named exit and the
calling questphase records the result.
| Record | Value |
|---|---|
| Practical baseline | Cyberpunk 2077 Windows GOG 2.31a, WolvenKit 8.19.0, ArchiveXL 1.27.0, RED4ext 1.30.0, redscript 0.5.31 |
| Focused vanilla comparison | Observed in vanilla |
| New mod-owned choice or multi-exit scene | Experimental until its own runtime matrix passes |
Clean-save requirement: scene state, quest checkpoints, journal entries, and result facts can survive a rebuild. Keep one untouched save from before the scene has ever started. Test each outcome from a closed-game copy of that same save, and use a new result-fact namespace when an older candidate may already have written state.
This guide teaches the native resource joins. It does not require a scene generator, manifest, explorer, or custom compiler.
The five ownership layers
screenplayStore.options[]
itemId + locstringId
|
+-> locStore.vdEntries[] -> vpeIndex -> locStore.vpEntries[]
|
`-> scnChoiceNodeOption.screenplayOptionId
|
v
choice output socket ordinal
|
v
section or branch graph
|
v
scnEndNode + named exit point
|
v
questSceneNodeDefinition output -> durable fact
| Layer | Native owner | What it identifies |
|---|---|---|
| Screenplay option | scnscreenplayChoiceOption | One choice item and its scnlocLocstringId |
| Choice presentation | scnChoiceNodeOption | The choice item shown by one choice node, plus its conditions and interaction styling |
| Localized label | scnlocLocStoreEmbedded | Descriptor-to-payload lookup for the option locstring |
| Local branch | scnChoiceNode.outputSockets | Which scene node receives the selected option |
| Quest result | scnExitPoint and the calling quest scene node | Which named scene route the questphase receives |
The repeated small integers in these layers are not interchangeable. A
scnscreenplayItemId, a choice output ordinal, a scnNodeId, a
scnlocLocstringId, a scnlocVariantId, and a quest fact value occupy
different ID domains.
A focused vanilla choice shape
Extract this scene from your own game installation and inspect choice node
43:
base\quest\minor_quests\mq003\scenes\mq003_01_homeless.scene
The installed Cyberpunk 2.31a resource contains two options:
Node 43 row | Screenplay item | Output stamp | First destination |
|---|---|---|---|
Option 0 | 2 | name 0, ordinal 0 | node 48 |
Option 1 | 258 | name 0, ordinal 1 | node 45 |
After those option outputs, the node retains six unconnected output sockets
with names 1 through 6, each at ordinal 0. The same option-count-plus-six
shape appears in the audited mq003, mq007, and mq010 choices. This is
Observed in vanilla for those resources, not proof that every scene type,
game version, or special interaction must use the same padding.
Each node option points back to screenplayStore.options:
scnChoiceNodeOption.screenplayOptionId = 2
-> scnscreenplayChoiceOption.itemId = 2
-> scnscreenplayChoiceOption.locstringId.ruid
caption on scnChoiceNodeOption is an editor-facing label in the inspected
resources. It is not the player-visible localization payload. Likewise,
isSingleChoice and gameinteractionsChoiceTypeWrapper.properties are
separate stored values. Do not derive optionality, progression importance,
color, or repeatability from either field alone. Copy those semantics from the
closest relevant vanilla choice and test the resulting interaction.
Embedded choice localization
Spoken dialogue and choice labels use different ownership in the scene model:
| Content | Scene field | Text owner |
|---|---|---|
| Spoken line | screenplayStore.lines[].locstringId | External subtitle resource; VO is a separate external map/WEM branch |
| Choice label | screenplayStore.options[].locstringId | The scene’s embedded scnlocLocStoreEmbedded |
For one option, the embedded lookup is:
screenplay option locstringId
-> vdEntries[] row with the same locstringId and requested locale
variantId
vpeIndex
-> vpEntries[vpeIndex]
matching variantId
content
Every descriptor in the four audited scene exports points at an in-range
payload whose variantId matches the descriptor. A descriptor’s vpeIndex is
an array index, not a locstring ID or CR2W handle.
Preserve observed ordering without inventing a universal rule
The focused exports at the four depot paths listed below have contiguous
db_db, pl_pl, and en_us descriptor blocks. Within each block,
vdEntries are sorted by the unsigned numeric value of locstringId.ruid.
That arrangement is Observed in vanilla in this corpus.
Do not sort decimal IDs as strings: 100 sorts before 20 lexically but
after it numerically. Preserve every value as an unsigned 64-bit number through
comparison and serialization.
Several db_db locstrings have two descriptors and two payloads, commonly one
blank and one source-text payload. Their relative payload order differs among
the audited scenes. Therefore:
- keep duplicate descriptors adjacent within their numeric locstring group;
- preserve the descriptor,
variantId,vpeIndex, and payload relationship from the evidence-matched source shape; - do not declare either blank-first or text-first a universal engine rule;
- do not replace the
db_dbblock withen_usmerely because the displayed language is English.
The exact required locale set for a newly authored scene, localization fallback rules, and behavior with a missing descriptor remain Experimental until tested in that package.
Route a choice to a quest outcome
There are two useful scene designs:
local conversation branch
Choice -> reply A --+
-> reply B --+-> shared section -> one named exit
quest-significant branch
Choice -> accept section -> End A [accept]
-> refuse section -> End B [refuse]
The first keeps narrative variation inside the scene. The second exposes a result to the quest. For the second design, all of these names and IDs must agree:
- each option socket reaches the intended branch;
- each branch reaches its own
scnEndNode; - every End is present in
sceneGraph.endNodes; exitPointsmaps a stable CName such asacceptorrefuseto that End;- the calling
questSceneNodeDefinitionhas an output socket with the exact same name; - each quest output immediately writes the intended durable result fact;
- shared continuation converges only after branch-owned effects are done.
A caption, locstring RUID, or option ordinal is not a quest output name. Do not make a later phase rediscover the result from presentation state. See Branching, choices, and debriefs for fact and convergence design.
Lab 5 supplies a Structurally validated single named-exit handoff. For a choice scene, test every option and named exit, then repeat after interruption and reload.
Manual WolvenKit authoring procedure
No downloadable choice checkpoint is claimed by this page. Use the Lab 5 project only as a Structurally validated v5 scene/quest handoff starting point; the added choice remains a new Experimental candidate.
- In WolvenKit
8.19.0, extract one evidence-matched vanilla.scenefor inspection. Do not add the extracted resource to your mod or redistribute its complete CR2W or serialized export. - In a mod-owned v5 scene, allocate unique
scnscreenplayItemIdvalues for the options. The audited ordinary option pattern is2 + 256n; treat that as Observed in vanilla, not an unchecked license to collide with existing items. - Add one
scnscreenplayChoiceOptionper visible option. SetitemId, a full unsignedlocstringId.ruid, and its usage fields deliberately. - Add the matching
scnChoiceNodeOptionrows. Match eachscreenplayOptionIdto one screenplay option and copy the remaining option semantics from the closest compatible vanilla case. - Add one output stamped name
0and ordinal0..n-1for each option. Add the six observed padding sockets only when the chosen compatible shape uses them. Connect every option output; leave padding sockets as the comparison does. - Populate
locStore.vdEntriesandvpEntries. Preserve typed IDs, locale-block structure, unsigned numeric ordering, duplicate descriptor relationships,vpeIndex, and matching payloadvariantIdvalues. - Build each branch as ordinary scene nodes and sections. Decide whether it rejoins locally or reaches a distinct End and named exit.
- If the quest consumes several exits, add exact matching output sockets to
its
questSceneNodeDefinitionand write one durable result on each route. - Save, close, and reopen the scene. Convert it to focused CR2W JSON and
inspect item joins, descriptor/payload joins, output stamps, graph edges,
endNodes, and named exits. A clean WolvenKit round trip is Structurally validated evidence only. - Pack the normal WolvenKit project and inspect ArchiveXL/game logs before running the clean-save matrix.
Do not paste an entire choice node from an unrelated scene and leave its actor, conditions, mappin, reminder, persistent-line, timing, or attachment data in place. Those fields are part of the interaction contract even when they are not visible in a graph diagram.
Acceptance and lifecycle matrix
| Case | Required observation |
|---|---|
| Every option from the untouched pre-scene save | Correct label, branch, spoken response, named exit, and result fact |
| Optional branch then progression choice | Optional branch does not consume or duplicate the required progression choice |
| Save before the choice appears | Reload presents one coherent choice group and no stale label |
| Save after selection, before named exit | Reload resumes the selected branch without reopening another branch |
| Save immediately after named exit | Quest writes exactly one result and does not launch the scene again |
| Interrupt before selection | Return behavior is coherent and no result is written |
| Interrupt after selection | Branch and return behavior match the declared design; no other outcome fires |
| Missing-locale negative control | Deliberately recorded fallback behavior; never inferred from the editor caption |
| Completed-save reload | Choice, scene, reward, and branch-only effects do not repeat |
| Mod removal from a disposable save | No claim of cleanup unless active scene, quest, fact, and journal state are explicitly observed |
Active-scene saving may be restricted by the game. Record the nearest available checkpoint instead of calling an unexecuted save case a pass. Multi-exit interruption, return, active-branch reload, and localization fallback remain Experimental until the exact candidate passes these cases.
Failure routing
| Symptom | Inspect first |
|---|---|
| Option row is present but label is blank | Screenplay option locstring, locale descriptor coverage, vpeIndex, descriptor/payload variantId, and block order |
| One option shows another option’s text | Unsigned numeric sort, duplicate descriptor grouping, stale locStore payload, or an option pointing at the wrong locstring |
| Editor caption is correct but game text is wrong | Embedded locStore; caption is not the display source |
| Selecting option B follows branch A | Option array order, output name 0 ordinal, destination node, and stale active-scene save |
| Choice appears but cannot progress | Choice type/conditions, missing output edge, required persistent-line behavior, or a branch section that never ends |
| Scene reaches End but quest stalls | End missing from endNodes, missing/wrong exitPoints name, or quest scene-node socket mismatch |
| Correct scene branch, wrong later debrief | Durable result was not written immediately after the named output |
| Rebuild still shows old options | Starting save/checkpoint state, active scene state, archive replacement, and whether the game was fully restarted |
Vanilla references
Observed in vanilla: the focused choice, socket, screenplay, and embedded
localization shapes described above were inspected in these installed
Cyberpunk 2.31a resources:
base\quest\minor_quests\mq003\scenes\mq003_01_homeless.scenebase\quest\minor_quests\mq003\scenes\mq003_03_orbital_pod.scenebase\quest\minor_quests\mq007\scenes\mq007_01_gun_found.scenebase\quest\minor_quests\mq010\scenes\mq010_02_barry_talk.scene
Extract them from your own installation and record only focused fields. Do not publish the extracted resources or complete serialized payloads.
Structurally validated: Lab 5 proves a v5 scene, typed empty embedded
locStore, and one named scene exit survive a WolvenKit 8.19.0 round trip. It
does not contain a choice.
Custom choices are Experimental. Test every option, locale, named exit, interruption route, reload, and cleanup transition that your scene supports.
Previous: Cleanup and save state. Next: External VO, WEM, and lipsync.
External VO, WEM, and lipsync
Voice playback and lip movement are parallel resource chains. A scene line selects external subtitle and voice audio with one localization RUID, while each performer selects a lipsync animation-set slot from the scene root. A working subtitle does not prove either of the other chains.
| Record | Value |
|---|---|
| Practical baseline | Cyberpunk 2077 Windows GOG 2.31a, WolvenKit 8.19.0, ArchiveXL 1.27.0, RED4ext 1.30.0, redscript 0.5.31 |
| Lab 5 external subtitle/VO resource chain | Structurally validated |
| Focused vanilla lipsync slots and localized assets | Observed in vanilla |
| Exact Lab 5 playback | Verify with the Lab 5 subtitle, audio, reload, and cleanup cases |
| Arbitrary WEM encoding and custom lipsync generation | Experimental |
Clean-save requirement: an active scene and its checkpoint may retain actor, line, and interruption state across archive changes. Restart the game after replacing localization or audio resources, then begin acceptance from an untouched pre-scene save. Do not diagnose a new WEM from a save captured during the old line.
Use WolvenKit to author the native resource links. Preparing a WEM or custom facial animation also requires a compatible audio or animation tool.
Three independent lookup chains
one unsigned line RUID
|
+---------------------+---------------------+
| |
v v
ArchiveXL subtitles registration ArchiveXL VO-map registration
-> subtitle map -> locVoiceoverMap
-> subtitle-entry resource -> locVoLineEntry.stringId
-> subtitle entry stringId -> femaleResPath / maleResPath
-> displayed text -> archived WEM bytes
scene actor/player actor
-> lipsyncAnimSet.id
-> scene.resouresReferences.lipsyncAnimSets[id]
-> addressable .anims resource
-> facial/lipsync animation data
| Question | Correct owner |
|---|---|
| Which sentence is displayed? | External subtitle entry selected by the line RUID |
| Which audio file is played? | External locVoiceoverMap entry selected by the same RUID |
| Which gender-specific audio path is used? | femaleResPath or maleResPath on that VO entry |
| Which mouth/gesture animation table is available to an actor? | The actor’s scene-local lipsync slot and the root lipsyncAnimSets collection |
| When does the line start and how long can it run? | scnDialogLineEvent and its owning section |
A WEM is not embedded in screenplayStore, and a lipsync .anims row is not
the VO file. The WEM path does not determine the actor’s slot. The actor’s slot
does not repair a missing VO-map entry.
The external subtitle and WEM route
Lab 5 provides a focused, mod-owned chain:
| Role | Exact depot path |
|---|---|
| Subtitle map | mod\cqa\cqa005\localization\en-us\subtitles\cqa005_subtitles_map.json |
| Subtitle entries | mod\cqa\cqa005\localization\en-us\subtitles\cqa005_subtitles.json |
| VO map | mod\cqa\cqa005\localization\en-us\vo\cqa005_vo.json |
| Synthetic voice asset | mod\cqa\cqa005\localization\en-us\vo\contact_i_85c3283507e7ef2f.wem |
Its line, subtitle entry, and locVoLineEntry all use unsigned RUID
9638591835734011695. The VO entry points both gender fields at the same
synthetic WEM. That is an exact lab choice, not a rule that female and male
paths must be equal.
ArchiveXL registers the subtitle map under
localization.subtitles.en-us and the VO map under
localization.vomaps.en-us. The subtitle map reaches the subtitle-entry
resource; the VO map reaches WEMs through its line entries. The subtitle-entry
resource and each WEM are archive dependencies, not separate ArchiveXL root
registrations in this design.
Use the same unsigned RUID at the semantic join, but keep these other identifiers independent:
| Typed or path domain | Purpose |
|---|---|
scnscreenplayItemId | Joins the timed dialog event to the screenplay line |
scnSceneEventId | Identifies that timed event instance |
scnlocLocstringId.ruid | Joins line, subtitle entry, and VO entry |
| WEM depot path | Locates audio bytes selected by the VO entry |
scnLipsyncAnimSetSRRefId.id | Indexes the scene root’s lipsync rows |
Do not derive one domain from another merely because a filename happens to contain a hexadecimal suffix.
Lipsync ownership and slot cardinality
The scene root property is spelled resouresReferences. Its
lipsyncAnimSets array contains scnLipsyncAnimSetSRRef rows. Every ordinary
actor and player-actor definition selects one row through
scnLipsyncAnimSetSRRefId.id.
For a cooked scene with N addressable rows, every selected ID must satisfy:
0 <= actor.lipsyncAnimSet.id < N
This is a cardinality check, not a quality check. It proves that the lookup is in range; it does not prove that the resource matches the actor’s rig, language, gender, line set, or desired facial performance.
Distinct vanilla rows
Observed in vanilla: in
base\quest\minor_quests\mq007\scenes\mq007_01_gun_found.scene, the non-player
actor selects slot 0 and V selects slot 1. The installed English
localization archive contains distinct assets at:
base\localization\en-us\lipsync\base\quest\minor_quests\mq007\scenes\mq007_01_gun_found\skippy.animsbase\localization\en-us\lipsync\base\quest\minor_quests\mq007\scenes\mq007_01_gun_found\v.anims
That resource proves the two-slot shape for this scene. It does not make those assets appropriate for another actor or permit redistribution.
Avoid duplicate-path slots
Two identical source paths may cook to one addressable import. Inspect the
cooked table before assigning an actor slot 1. Prefer distinct compatible
resources when the NPC and V require different facial performances.
Lab 5 deliberately keeps both actors on slot 0 with the generic resource:
base\animations\facial\generic\interactive_scene\generic_facial_lipsync_gestures.anims
That one-row resource and both indices are Structurally validated. Exact facial and gesture quality remains Experimental.
Check the selected clip and its skeletal reference
An addressable .anims resource is only the first lookup. For a custom line,
inspect the screenplay line’s femaleLipsyncAnimationName and
maleLipsyncAnimationName CNames and confirm that the selected clips exist in
the intended animation set. Keep these clip names separate from the WEM path
and localization RUID. Lab 5 leaves both names at None; it does not teach
custom clip generation.
A clip can import successfully, resolve by name, and match the audio duration while still deforming the actor’s head. When removing unwanted donor body motion, preserve the neutral translation, rotation, and scale reference used by the additive animation.
For a custom facial clip, inspect these separately:
| Check | Why it matters |
|---|---|
| Selected clip exists | A valid animset slot does not guarantee a matching line clip |
| Clip and audio durations agree | Inherited donor motion can extend a short clip |
| Facial keys survive import | Duration alone says nothing about mouth movement |
| Skeletal reference survives import | Removing unwanted body motion must not remove the additive reference pose |
| Rig bone and facial-track ordering agree | A matching bone count alone cannot establish compatibility |
Reopen the cooked asset in WolvenKit and inspect an export made from that asset, rather than checking only the source animation. Compare the skeleton with its compatible rig reference. Joint and key counts depend on the rig; matching those counts alone does not validate the reference pose.
Custom facial playback remains Experimental. Test each line in game, including interruption and reload, before using the clip in a finished scene.
Separate asset validity from lookup validity
Diagnose an audio/lipsync failure in this order:
- Line scheduling: confirm the section reaches the
scnDialogLineEvent, itsscreenplayLineIdresolves, and the duration does not clip the line. - Subtitle join: confirm the exact unsigned RUID, subtitle-map
registration, subtitle-entry path, locale, and matching
stringId. - VO join: confirm VO-map registration, exact
locVoLineEntry.stringId, selected gender path, archive presence, path case, and game/audio logs. - WEM bytes: confirm the authored asset is a complete playable WEM made from audio you have the right to distribute. Validate duration and decode independently of the scene.
- Actor readiness: confirm the intended actor was acquired and the event’s performer maps to it.
- Lipsync cardinality: inspect the cooked/round-tripped root collection and every selected slot, not only the raw source rows.
- Presentation compatibility: only after startup and lookup are stable, evaluate mouth movement, emotion, gesture, rig fit, and interruption.
This ordering distinguishes common boundaries:
| Observation | Most likely branch to inspect first |
|---|---|
| Subtitle absent and audio silent | Line event/RUID or both localization registrations |
| Subtitle present, audio silent | VO map, selected WEM path, WEM presence/bytes, audio logs |
| Audio present, mouth motion wrong | Lipsync resource, actor slot, selected clip name, rig compatibility, line-specific animation data |
| Head stretches only during speech | Additive skeletal reference and rig/track ordering in the cooked clip; compare with the idle appearance |
| Crash at scene launch before text/audio | Actor acquisition and cooked lipsync slot addressability |
| Correct first line, later line silent | Later line RUID, VO entry, WEM path/bytes, event timing |
| Works for one performer configuration only | Gender path, actor identity, slot/resource assignment, or rig compatibility |
Manual WolvenKit authoring procedure
- Start with a mod-owned v5 scene whose actor acquisition and one spoken-line event already round-trip. Record actor IDs, performer IDs, selected lipsync slots, line item ID, event ID, and full unsigned line RUID.
- Create a subtitle-entry resource containing the matching
stringId, then a subtitle map whose entry soft-references that resource. Keep both files under a stable mod depot path. - Create a
locVoiceoverMap. Add onelocVoLineEntrywith the same unsignedstringIdand deliberatefemaleResPathandmaleResPathvalues. - Add original or licensed WEM assets at those exact archive paths. If both fields select one file, document that choice; if they differ, test both selection routes. Test the selected encoder and codec with the resulting WEM.
- Register the subtitle map and VO map under the exact ArchiveXL locale branches. Do not register a subtitle-entry file as if it were the map, and do not register each WEM as a VO map.
- In
resouresReferences.lipsyncAnimSets, add only the lipsync resources the scene actually uses. Give each actor an in-range row ID. Do not manufacture two logical slots by duplicating one path without inspecting the cooked result. - Save and reopen every CR2W in WolvenKit
8.19.0. Convert focused resources to JSON and compare the RUID join, map references, gender paths, root array length, actor slot IDs, and event-to-line join. - Pack the normal project. List the archive and prove every referenced WEM is present at the exact path. Extract the cooked scene for a focused comparison of addressable lipsync rows.
- Fully restart the game, load the untouched pre-scene save, and run the acceptance matrix. Serialization, registration, and archive presence are Structurally validated evidence only.
Do not copy vanilla WEM or lipsync resources into your download. Cite their depot paths and teach readers to inspect their own installation. The synthetic Lab 5 WEM is mod-owned and may be used only under that example’s documented license and provenance.
Acceptance and lifecycle matrix
| Case | Required observation |
|---|---|
| Normal line from untouched pre-scene save | One subtitle, one audio playback, correct speaker, no launch crash |
| Every distinct line RUID | Correct text and WEM; no stale prior line |
| Every supported gender path | Correct path selection and asset, or documented shared-path behavior |
| Every actor slot | In-range after cooking; intended resource and acceptable facial result |
| Save before scene launch | Reload starts one coherent scene and resolves current assets |
| Save between lines | If permitted, no duplicate/omitted line or stale lipsync state |
| Interrupt and return | Audio, subtitle, actor tier, and lipsync recover without a false named exit |
| Stream away and return | Actor/resources reacquire without duplicate playback |
| Reload after named exit | No repeated VO or retained Cinematic AI/lipsync ownership |
| Missing-WEM negative control in a disposable candidate | Expected log/absence without misattributing the failure to lipsync |
Active-line save/reload, arbitrary encoder settings, generated custom lipsync, facial quality, interruption, and stream-return behavior remain Experimental until tested for the exact asset set.
Animation events and workspots
A scene can schedule skeletal animation directly, change an actor’s idle or look-at state, or ask a scene-local quest node to place an actor in a workspot. Those mechanisms overlap visually, but they have different resource owners, completion signals, placement rules, and cleanup obligations.
| Record | Value |
|---|---|
| Practical baseline | Cyberpunk 2077 Windows GOG 2.31a, WolvenKit 8.19.0, ArchiveXL 1.27.0, RED4ext 1.30.0, redscript 0.5.31 |
Focused mq007 and mq010 shapes | Observed in vanilla |
| Lab 5 empty animation collections and scene-local PuppetAI branch | Structurally validated |
| New cinematic animation, camera, look-at, or scene-workspot route | Experimental |
Clean-save requirement: active scene state, actor AI tier, workspot use, transforms, quest checkpoints, and community ownership may survive a rebuild or reload. Test from an untouched save made before actor activation and scene start. Give changed scene/workspot identities a new candidate namespace when an older save could retain them.
This page teaches native scene composition and inspection. It does not teach animation creation, rigging, retargeting, or Wwise/lipsync production, and it does not require a project-specific scene generator.
Pick the correct owner
| Desired behavior | Native mechanism | Primary completion/lifetime owner |
|---|---|---|
| Play a timed body clip inside a section | scnPlaySkAnimEvent | Event timing plus the owning section |
| Transition facial or idle state | scnChangeIdleAnimEvent | Event timing and subsequent reset/transition events |
| Aim eyes/head/body at a target | scnLookAtEvent | Start/stop request data and later scene events |
| Override player-camera parameters | scneventsCameraParamsEvent or a compatible camera event family | Event duration, reset flags, and scene interruption/exit |
| Put an actor into a scene-local workspot | scnQuestNode wrapping questUseWorkspotNodeDefinition | Workspot node outputs, actor AI, instance placement, and cleanup |
| Keep a community NPC at a world AI spot outside the scene | Community plus worldAISpotNode and .workspot | Community activation/phase and world streaming |
A direct animation event does not create a workspot or navigate an actor to
one. A workspot node does not schedule an arbitrary scnPlaySkAnimEvent in a
section. A world AI spot is not the same object as a scene-local
scnWorkspotInstance.
The cinematic animation join
The smallest useful engine view is a three-way join:
resouresReferences
cinematicAnimSets[slot] -> soft .anims depot path
cinematicAnimNames[slot] -> names available in that set
^
|
actor.bodyCinematicAnimSets[] -> slot
^
|
section.events[]
scnPlaySkAnimEvent.performer -> actor
scnPlaySkAnimEvent.animName -> a compatible available name
Focused mq007 observation
Inspect the installed resource:
base\quest\minor_quests\mq007\scenes\mq007_01_gun_found.scene
Its root contains cinematic animation slot 0 with this soft asset:
base\animations\quest\minor_quests\mq007\anim\body\mq007__talking_gun__male_fpp.anims
The corresponding cinematicAnimNames[0] row lists three names. V’s
bodyCinematicAnimSets contains scnCinematicAnimSetSRRefId(0). One focused
event then uses:
scnPlaySkAnimEvent property | Observed value |
|---|---|
| Performer | scnPerformerId(257) |
| Component | body |
| Animation name | stand__equip__skippy__01 |
| Event ID | unsigned 2159138510383108096 |
| Start/duration | 0 / 2600 ms |
| Body-part mask | spine_upper_body |
| Root motion enabled | 0 |
| Blend-in / blend-out | 0.5 / 0 |
This exact table is Observed in vanilla. The row proves how that scene connects an asset, an actor-visible slot, and a timed event. It does not prove that the animation is compatible with another rig, perspective, body type, component, mask, root-motion mode, or scene marker.
Event time is not graph completion
An animation event lives inside a section and has startTime and duration.
The section separately has its own duration and output sockets. Therefore:
- an event ending does not emit a questphase signal;
- a section that ends too early can clip an event;
- a section staying open longer can preserve blend-out or following events;
- an event ID is not an animation-set slot or graph-node ID;
- reaching the next scene node does not by itself reset AI, camera, look-at, or workspot state.
When events overlap, calculate the latest required event end and account for tested transition/reset time. Do not copy Lab 5’s 400 ms spoken-line tail as a universal animation constant.
Direct animation, idle, look-at, and camera boundaries
The mq010 scene contains several event families in the same sections:
base\quest\minor_quests\mq010\scenes\mq010_02_barry_talk.scene
Observed in vanilla: focused inspection finds scnPlaySkAnimEvent,
scnChangeIdleAnimEvent, scnLookAtEvent, and
scneventsCameraParamsEvent rows. Their decisive responsibilities differ:
| Event family | Fields to audit first | Common missing boundary |
|---|---|---|
| Play skeletal animation | performer, component, anim name, set visibility, start/duration, blend, root motion, mask | Rig/asset compatibility and exit pose |
| Change idle/facial state | performer, facial keys, transition, duration, enabled state | Explicit transition/reset on every exit |
| Look at target | source performer, target performer/actor/prop, target slot, start/stop request, transition and limits | Clearing or replacing the request after branch/interruption |
| Camera parameters | player-camera flag, camera ref, FOV/DOF overrides, duration, reset flags | Restoring camera state on interrupt, return, and named exit |
Copying one event without its partner reset, target, animation-set row, or section timing creates a different contract. A visually acceptable normal run does not prove interruption or reload cleanup.
The scene-workspot identity chain
A scene-local workspot has more identities than the .workspot path:
workspots[]
scnWorkspotData_ExternalWorkspotResource
dataId ------------------------------+
workspotResource -> .workspot |
v
workspotInstances[] dataId
workspotInstanceId <--------------------+
localTransform + originMarker
^
|
scnQuestNode / questUseWorkspotNodeDefinition
entityReference -> actor
paramsV1.workspotInstanceId
Work Started / Success outputs
Focused mq010 observation
One inspected route in mq010_02_barry_talk.scene joins:
| Layer | Exact observed value |
|---|---|
| External workspot resource | base\workspots\common\wall\generic__stand_wall_lean_left__stand_around__01.workspot |
scnSceneWorkspotDataId | 4010771317 |
scnSceneWorkspotInstanceId | 241 |
| Wrapper scene node | scnQuestNode 241 |
| Inner native node | questUseWorkspotNodeDefinition, ID 241 |
| Actor target | entity reference #mq010_com_barry, name barry |
| Params type | scnUseSceneWorkspotParamsV1 |
| Workspot outputs | Work Started, Success |
| Debug symbol | workspot instance 241 mapped to scene node 241 |
The repeated value 241 is the arrangement in this one route; it does not
collapse the typed ID domains. Its data ID remains a distinct value, and other
vanilla resources may choose different node/instance relationships.
The instance also owns a local transform and an originMarker. That placement
is interpreted in the scene’s location context. A valid .workspot path does
not supply the correct actor, transform, scene marker, navigation, or
interruption behavior.
The observed params include fields such as changeWorkspot, entryId,
instant, isPlayer, isWorkspotInfinite, jumpToEntry, movementType,
teleport, and player camera settings. Do not copy those values as a generic
recipe. Extract a route whose player/NPC role, movement, camera, entry, and
lifetime match the intended beat, then preserve and test its complete
contract.
For persistent world AI spots and device-owned interaction workspots, use Workspots and interactions. The scene-local rows described here do not replace community or device ownership.
Manual WolvenKit authoring procedure
No downloadable cinematic-animation or scene-workspot checkpoint is claimed by this page. Lab 5 supplies a Structurally validated v5 scene with typed empty collections; every added event, asset slot, or workspot route is a new Experimental candidate.
Add one direct animation event
- Extract an evidence-matched vanilla scene and the referenced
.animsmetadata from your own installation for inspection. Cite the asset; do not redistribute it. - Confirm the target actor’s rig, perspective, component, body type, and acquisition route match the comparison closely enough to justify a test.
- In the mod-owned scene root, add one
scnCinematicAnimSetSRReftocinematicAnimSetswith the exact soft depot path and deliberate override/priority values. - At the matching collection slot, add
scnAnimSetAnimNamescontaining only the animation names expected from that set. Add the slot’sscnCinematicAnimSetSRRefIdto the intended actor’sbodyCinematicAnimSets. - Add one
scnPlaySkAnimEventto a section. Assign a unique full unsignedscnSceneEventId, the correct performer, component, animation name, start/duration, blend, mask, and root-motion settings. - Size the section and subsequent transition deliberately. Add required reset/idle/look-at/camera events rather than assuming End cleans them up.
- Save and reopen the scene in WolvenKit
8.19.0. Convert a focused copy to JSON and verify collection indices, actor visibility, event name, performer, timing, and all typed IDs.
Add one scene-local workspot route
- Add a
scnWorkspotData_ExternalWorkspotResourcerow with a uniquescnSceneWorkspotDataIdand evidence-matched.workspotdepot path. - Add one
scnWorkspotInstancewhosedataIdselects that row. Allocate a unique typed instance ID, then set its origin marker and local transform. - Add/update the corresponding
scnWorkspotSymboldebug row if the chosen vanilla-compatible shape uses it. - Add an
scnQuestNodewrapper aroundquestUseWorkspotNodeDefinition. Target the acquired actor exactly and usescnUseSceneWorkspotParamsV1.workspotInstanceIdto select the instance. - Copy the complete params/socket shape from a compatible player or NPC case;
do not infer
teleport,instant, infinite use, entry IDs, or camera behavior from their names alone. - Route
Work Started,Success, interruption, and cut behavior according to the narrative requirement. Decide which output is allowed to advance the scene and which owner releases the actor. - Inspect data/instance/node/debug joins after a WolvenKit round trip. Pack and list the archive before entering the game.
Reopen the cooked resource, then test animation playback, navigation, camera framing, workspot completion, and cleanup in game.
Acceptance and lifecycle matrix
| Case | Required observation |
|---|---|
| Normal approach from untouched save | Actor reaches expected start state; event/workspot starts once |
| Animation start and end frames | No T-pose, snap, clipping, wrong body component, or truncated blend |
| Root-motion route | Final transform and facing match the design without collision/navmesh escape |
| Workspot entry from several directions | Navigation/teleport policy, local transform, facing, and clearance are coherent |
| Branch bypasses the event | No animation, look-at, camera, or workspot state leaks from an unentered branch |
| Interrupt during event/workspot | Actor, AI tier, camera, look-at, and graph route recover deliberately |
| Return after interruption | Beat resumes or restarts exactly as specified; no duplicate side effect |
| Save/reload during the beat, if permitted | No stuck pose, missing actor, duplicate event, or false Success |
| Stream away and return | Community actor and scene workspot reacquire without duplicate ownership |
| Named scene exit | Camera/look-at/AI/workspot state is released before the quest cleans up the actor |
| Completed-save reload and mod removal | No replay or retained actor ownership; removal is tested only on a disposable save |
If the game forbids saving during a cinematic state, record that limitation and test the nearest checkpoint. New animation playback, root motion, workspot entry, camera behavior, interruption, return, reload, and cleanup remain Experimental until the exact route passes.
Failure routing
| Symptom | Inspect first |
|---|---|
| Event runs but actor does not move | Performer mapping, actor-visible set slot, animation name, component/mask, and asset compatibility |
| T-pose or severe deformation | Rig/body/perspective mismatch, wrong animation set, missing linked channels |
| Animation is clipped | Event start/duration, section duration, blend-out, and following transition |
| Actor snaps or ends in the wrong place | Root-motion enabled/mode, origin marker, offset, scene marker, and collision |
| Look-at persists after the line | Missing stop/replacement request or untested interruption/exit cleanup |
| Camera remains constrained | Reset flags/events, player-camera ownership, named exit, and interruption path |
| Actor never enters workspot | Actor target, data-to-instance join, instance transform, entry params, navigation, streaming |
Work Started fires but scene stalls | Wrong output consumed, Success semantics, infinite/finite params, or missing wrapper mapping |
| Workspot works once but fails after reload | Saved scene/community/workspot state, retained actor role, stale instance identity |
| Actor disappears at scene exit | Community cleanup/deactivation raced animation/workspot release |
Vanilla references
Observed in vanilla: the focused fields and paths in this guide were
inspected in installed Cyberpunk 2.31a resources:
base\quest\minor_quests\mq007\scenes\mq007_01_gun_found.scenebase\animations\quest\minor_quests\mq007\anim\body\mq007__talking_gun__male_fpp.animsbase\quest\minor_quests\mq010\scenes\mq010_02_barry_talk.scenebase\workspots\common\wall\generic__stand_wall_lean_left__stand_around__01.workspot
These are citations. Extract focused references from your own installation; do not publish the vanilla CR2W resources or full serialized exports.
Lab 5 supplies empty animation/workspot collections and a scene-local PuppetAI wrapper. Add and test your own animation or workspot route separately.
New animation, camera, and workspot combinations are Experimental until tested through normal completion, interruption, return, and reload.
Previous: External VO, WEM, and lipsync. Back to: Scenes.
Lab 5: First Contact
Lab 5 combines the systems from the first four labs into one small contact encounter. A registered root calls an external child. The child activates a one-entry community, waits for the actor and player to become ready, starts a one-line scene through its named entry, consumes the named scene exit, and delays cleanup until V has left the outer area.
| Record | Value |
|---|---|
| Structural validation date | 2026-08-09 |
| Runtime test date | Not yet recorded |
Implementation status: both supplied checkpoints contain eleven mod-owned
CR2W resources plus one WEM. The CR2W pairs are Structurally validated with
WolvenKit 8.19.0; the WEM’s container, format, duration, path, and hash are
checked separately. Mounting, actor materialization, scene playback, subtitle
and VO lookup, named return, stream-away/return, post-contact_done reload,
completed reload, pre-scene save loads, and cleanup are covered by the Lab 5
test procedure.
What the lab demonstrates
registered root -> cqa005_completed == 0?
False -> terminate
True -> activate First Contact
-> enter external child through In1
activate meet objective and map pin
Activate contact/default
wait CharacterSpawned > 0 for the whole community
wait until V is inside the broad setup area
create checkpoint
enter scene through start
contact says one externally localized line
scene exits through contact_done
succeed meet; retire pin; activate leave
wait until V is outside the cleanup area
Deactivate the whole community
succeed leave; return through Out1
<- succeed quest; set cqa005_completed = 1; terminate
The two waits before the scene solve different races. CharacterSpawned
observes the community, while IsInside #cqa005_tr_setup controls player
placement. Neither is a substitute for the other. The outer cleanup wait keeps
the actor alive after the named scene exit and avoids removing the contact in
front of the player.
Required environment
| Component | Exact version |
|---|---|
| Cyberpunk 2077 for Windows (GOG) | 2.31a (public patch 2.31) |
| WolvenKit | 8.19.0 |
| ArchiveXL | 1.27.0 |
| RED4ext | 1.30.0 |
| redscript | 0.5.31 |
Other versions may serialize different wrappers or produce different runtime behavior.
Prerequisites and downloads
Complete Lab 4, then read:
-
Download the start checkpoint. It has the complete twelve-artifact scaffold, a three-node root, a two-node child, and a two-node
start -> contact_donescene shell. -
Download the completed checkpoint. It has the same ownership boundary with the seven-node root, fifteen-node child, and four-node scene described below.
Do not install both checkpoints. They register and pack the same depot paths.
For frozen runtime acceptance, establish the two documented untouched manual
originals, each of which has never loaded any CQA Lab 1–5 candidate. Case 1
then creates the exact pre-scene, post-contact, and completed manual seeds
under the unchanged canonical candidate; later cases use byte-identical
full-slot clones. Labs 3 and 4 share this site, and removing their files does
not erase their saved state. A console reset of cqa005_completed likewise
does not remove save-backed quest, journal, scene, community, actor, or
streamed-world state. See the test chapter for the five capture IDs and fan-out.
Twelve runtime artifacts
| # | Artifact | Depot path | Owner or purpose |
|---|---|---|---|
| 1 | Root questphase | mod\cqa\cqa005\phases\cqa005.questphase | Registration, one-shot guard, root prefab, child call, quest completion |
| 2 | Contact child questphase | mod\cqa\cqa005\phases\cqa005_contact.questphase | Meet, readiness, scene handoff, leave, and cleanup lifecycle |
| 3 | Scene | mod\cqa\cqa005\scenes\cqa005_first_contact.scene | Actors, screenplay item, line event, graph, entry, and named exit |
| 4 | Journal | mod\cqa\cqa005\journal\cqa005.journal | Quest, phase, two objectives, and contact map pin |
| 5 | Onscreen localization | mod\cqa\cqa005\localization\en-us\onscreens\cqa005_onscreens.json | Quest, objective, and map-pin text |
| 6 | Subtitle map | mod\cqa\cqa005\localization\en-us\subtitles\cqa005_subtitles_map.json | Registered route to the subtitle entries resource |
| 7 | Subtitle entries | mod\cqa\cqa005\localization\en-us\subtitles\cqa005_subtitles.json | Male and female text for locstring 9638591835734011695 |
| 8 | VO map | mod\cqa\cqa005\localization\en-us\vo\cqa005_vo.json | Male and female audio lookup for the same locstring |
| 9 | WEM | mod\cqa\cqa005\localization\en-us\vo\contact_i_85c3283507e7ef2f.wem | Prebuilt synthetic spoken audio |
| 10 | Streaming block | mod\cqa\cqa005\world\cqa005_first_contact.streamingblock | Quest and AlwaysLoaded sector descriptors |
| 11 | Quest sector | mod\cqa\cqa005\world\cqa005_first_contact.streamingsector | Setup/cleanup triggers, AI spot, and compiled community area |
| 12 | AlwaysLoaded sector | mod\cqa\cqa005\world\cqa005_always_loaded.streamingsector | Scene marker, map marker, and community registry |
The WEM is not a CR2W file and has no CR2W-JSON counterpart. Each checkpoint
therefore contains eleven raw/cooked CR2W pairs and one archived WEM. Its
source WAV and production record live outside the WolvenKit source tree so
they cannot be packed accidentally.
Registration versus indirect dependencies
ArchiveXL registers six roots:
quest:
phases:
- path: mod\cqa\cqa005\phases\cqa005.questphase
parent: base\quest\cyberpunk2077.quest
journal:
- mod\cqa\cqa005\journal\cqa005.journal
localization:
onscreens:
en-us:
- mod\cqa\cqa005\localization\en-us\onscreens\cqa005_onscreens.json
subtitles:
en-us:
- mod\cqa\cqa005\localization\en-us\subtitles\cqa005_subtitles_map.json
vomaps:
en-us:
- mod\cqa\cqa005\localization\en-us\vo\cqa005_vo.json
streaming:
blocks:
- mod\cqa\cqa005\world\cqa005_first_contact.streamingblock
The child phase, scene, subtitle entries, WEM, and two sectors are archived
dependencies. They are reached respectively by phaseResource, sceneFile,
the subtitle map, the VO map, and the streaming-block descriptors. Registering
those indirect resources as additional roots changes the ownership model and
is rejected by the supplied validator.
Root and child ownership
The registered root declares one prefab:
#cqa005_pr_first_contact
The child has phasePrefabs: [], and the external phase node has
phaseInstancePrefabs: []. The root’s world-side expansion is:
$/mod/cqa/cqa005/#cqa005_pr_first_contact
#cqa005_tr_setup
#cqa005_tr_cleanup
#cqa005_spot_contact
#cqa005_com_contact
#cqa005_sm_contact
#cqa005_mp_contact
The two marker nodes and registry live in the AlwaysLoaded sector. The two triggers, AI spot, and compiled area live in the finite Quest sector. Root ownership does not make every nested node AlwaysLoaded; the block descriptors still decide sector lifetime.
Exact root graph
| ID | RED node type | Decisive payload |
|---|---|---|
0 | questInputNodeDefinition | Root In1 interface |
1 | questOutputNodeDefinition | Terminating Out1 |
10 | questConditionNodeDefinition | cqa005_completed Equal 0 |
11 | questJournalNodeDefinition | Set First Contact quest Active |
12 | questPhaseNodeDefinition | Soft cqa005_contact.questphase; null inline graph; empty instance prefabs |
13 | questJournalNodeDefinition | Set First Contact quest Succeeded |
14 | questFactsDBManagerNodeDefinition | Set cqa005_completed = 1 exactly |
Its seven edges are:
0.Out -> 10.In
10.False -> 1.In
10.True -> 11.Active
11.Out -> 12.In1
12.Out1 -> 13.Succeeded
13.Out -> 14.In
14.Out -> 1.In
The completion fact is deliberately written only after the child returns and the quest reaches succeeded state. This is the candidate’s exact policy, not a universal ordering rule.
Exact contact child
| ID | RED node type | Purpose |
|---|---|---|
0 | questInputNodeDefinition | Receive In1 |
1 | questOutputNodeDefinition | Return terminating Out1 |
10 | questJournalNodeDefinition | Activate meet objective |
11 | questMappinManagerNodeDefinition | Activate contact pin |
12 | questSpawnManagerNodeDefinition | Activate contact/default |
13 | questPauseConditionNodeDefinition | Wait CharacterSpawned Greater 0, entireCommunity: 1 |
14 | questPauseConditionNodeDefinition | Wait player IsInside #cqa005_tr_setup |
15 | questCheckpointNodeDefinition | Create the pre-scene checkpoint |
16 | questSceneNodeDefinition | Enter start; continue only from contact_done |
17 | questJournalNodeDefinition | Succeed meet objective |
18 | questMappinManagerNodeDefinition | Inactivate contact pin |
19 | questJournalNodeDefinition | Activate leave objective |
20 | questPauseConditionNodeDefinition | Wait player IsOutside #cqa005_tr_cleanup |
21 | questSpawnManagerNodeDefinition | Deactivate with entry/phase None for the whole community |
22 | questJournalNodeDefinition | Succeed leave objective |
The fourteen edges form one ordered chain from 0.Out to 1.In. Node 16
has exactly five sockets: CutDestination, input start, output
contact_done, and the unconnected Default INT and Default RET outputs.
There is no invented end output. The child continues only from the exit name
actually published by the scene.
Community and world identity contract
The canonical contact is a generic Tyger Claws record, not a unique story-managed actor:
characterRecordId:
Character.jpn_tyger_claws_biker2_ranged2_copperhead_wa
appearance: default
entry: contact
phase: default
workspot:
base\workspots\common\ground\
generic__stand_ground_cigarette__smoke__01.workspot
The registry sets entryActiveOnStart: 0, one quantity, and a non-sequence
time period. Quest logic activates it explicitly.
Three 64-bit identity domains stay distinct:
- the compiled area’s
sourceObjectIdequals the registry item’scommunityId; - the registry node placement has a different numeric world identity;
- the AI spot’s global ID is repeated by the compiled area’s
spotNodeIdsand registryworkspotsPersistentData.
All three are derived from full NodeRef-aware source strings. They are not
plain FNV hashes of debug labels, and the # alias marker has RED4-specific
hash behavior. The registry’s numeric placement identity is also not an
ordinary string entry in the sector’s nodeRefs array.
Check the contact for spawn, passivity, scene acquisition, cleanup, and visible workspot animation at the supplied location.
Exact scene contract
The completed scene graph has four nodes and three edges:
[1] Start -- output 0/0 --> [2] Section input 0/0
\-- same output --> [4] PuppetAI input 0/1
[2] Section -- output 0/0 --> [3] End (Terminating)
[4] PuppetAI -- output 0/0 --> no destination
The PuppetAI branch places the contact in the Cinematic AI tier and is
fire-and-forget. It does not delay the named exit. Section output stamp 1/0
is the deliberately unconnected cancel route. startNodes contains node 1,
endNodes contains node 3, entry start targets node 1, and exit
contact_done targets node 3.
Actor 0, contact, uses community acquisition for entry contact. Player
actor 1, V, uses findInContext with
Character.Player_Puppet_Base. Their performer IDs are 1 and 257; both
select lipsync slot 0. The scene retains one vanilla lipsync animation-set
reference by depot path without redistributing that resource.
The screenplay store contains one scnscreenplayDialogLine spoken by actor
0 to actor 1, with gender mask 3. One scnDialogLineEvent points to that
item. The line is:
All clear. Keep moving.
Its unsigned locstring ID is 9638591835734011695. The section lasts the
audio duration plus 400 ms. The embedded locStore remains typed and empty:
spoken text and sound come from external localization resources.
The start checkpoint retains the same actors, lipsync reference, entry, exit,
debug scaffold, and typed empty stores, but its graph is only Start 1 to
terminating End 3. Neither start phase graph invokes that scene shell.
Spoken-line lookup
scene line locstring 9638591835734011695
-> registered subtitle map
-> archived subtitle entries
femaleVariant = maleVariant = "All clear. Keep moving."
-> registered VO map
-> archived WEM for femaleResPath and maleResPath
The checkpoint includes a synthetic WAV and its prebuilt WEM. Use the supplied WEM for the lab; see External VO, WEM, and lipsync when preparing your own audio.
Geometry and delayed cleanup
The example reuses the checked Allen Street center from Labs 3 and 4:
| Owner | Center | Geometry |
|---|---|---|
| Setup trigger | (-1000.02, 1497.2208, 2.3) | 25 m circumradius, 16 points, 12 m high |
| Cleanup trigger | (-1000.02, 1497.2208, 0.3) | 110 m circumradius, 20 points, 16 m high |
| Contact AI spot | (-1000.02, 1497.2208, 6.957) | cigarette workspot, yaw 88.6 degrees |
| Scene and map markers | (-1000.02, 1497.2208, 8.3) | yaw 88.6 degrees |
The broad setup area makes a fast arrival less likely to outrun actor readiness. The much larger cleanup area keeps the contact present while V can still see the meeting point. These dimensions are a candidate design choice, not a universal safe radius.
Common failure modes
| Symptom | Check first |
|---|---|
| Quest never appears | Root registration, archive pair, ArchiveXL and RED4ext logs, clean starting save |
| Objective appears but contact does not | Quest-sector streaming, community/source join, entry and phase names, spot ID joins, CharacterSpawned payload |
| Contact spawns hostile | Generic character record, AI state, absence of unrelated combat stimuli, exact installed build |
| Contact exists but scene does not start | Setup trigger, scene marker, actor acquisition reference, named start socket |
| Mouth moves but there is no subtitle or sound | Locstring equality across scene, subtitle entries, and VO map; registered map paths; archived WEM path |
| Line plays but quest stalls | Scene exit contact_done, End 3, quest scene-node output name, unconnected default outputs |
| Contact disappears in view | Cleanup IsOutside radius and deactivation order |
| Completed save starts again | cqa005_completed, write order, exact installed candidate, and original save history |
Lab sequence: All labs · Next: Author First Contact in WolvenKit.
Author First Contact in WolvenKit
Build First Contact from the supplied start project in WolvenKit. The completed checkpoint is available alongside it for comparing resources and connections.
| Record | Value |
|---|---|
| Structural validation date | 2026-08-09 |
| Runtime test date | Not yet recorded |
Required environment and clean-save rule
| Component | Exact version |
|---|---|
| Cyberpunk 2077 for Windows (GOG) | 2.31a (public patch 2.31) |
| WolvenKit | 8.19.0 |
| ArchiveXL | 1.27.0 |
| RED4ext | 1.30.0 |
| redscript | 0.5.31 |
For frozen runtime acceptance, establish the two documented untouched manual originals, neither of which has ever loaded any CQA Lab 1–5 candidate. Keep them outside the game’s rotating autosave set. During Case 1, create the exact pre-scene, post-contact, and completed manual seeds under the unchanged canonical candidate; later cases use closed-game full-slot clones. Labs 3 and 4 share this site, and a save made with any prior tutorial checkpoint can retain quest, journal, trigger, or world state after its files are removed. A save made with the Lab 5 start checkpoint is also not clean for either original: both checkpoints use the same paths, journal IDs, scene identity, world identities, and fact.
1. Open and isolate the start checkpoint
-
Download and extract the start ZIP.
-
Make an untouched backup of the extracted checkpoint, then make a separate working copy. Keep WolvenKit closed while changing project identity.
-
In the working copy, rename
CQA_Lab05_FirstContact_Start.cpmodprojtoCQA_Lab05_FirstContact.cpmodproj. Open that XML file in a plain UTF-8 text editor and change only these two elements:<Name>CQA Lab 05 First Contact</Name> <ModName>CQA_Lab05_FirstContact</ModName> -
Rename
source\resources\CQA_Lab05_FirstContact_Start.archive.xltosource\resources\CQA_Lab05_FirstContact.archive.xl. Its YAML content and six registration roots do not change. -
Confirm the working copy has no
_Start.cpmodprojand no_Start.archive.xl, then openCQA_Lab05_FirstContact.cpmodprojwith File > Open Project. Reopen WolvenKit if it had cached the old project name. -
Confirm
source\archiveandsource\raweach contain the eleven CR2W paths in the overview. -
Confirm the archived WEM exists at
source\archive\mod\cqa\cqa005\localization\en-us\vo\contact_i_85c3283507e7ef2f.wem. -
Confirm the renamed loose ArchiveXL file is under
source\resourcesand the source WAV plus provenance are outsidesource. -
Do not install the untouched start backup and completed working project together.
The cooked files are the editable WolvenKit resources. The parallel CR2W-JSON files are focused review artifacts; do not edit JSON as the reader workflow.
2. Audit the closed resource inventory
Before touching a graph, account for every owner:
| Resource | Decisive check |
|---|---|
| Root phase | Three-node Input -> external Phase -> Output shell and one root prefab |
| Child phase | Two-node Input -> Output shell and no phase prefabs |
| Scene | Start 1 -> 3 End shell, actors, entry/exit, typed empty screenplay |
| Journal | Quest, phase, meet, leave, and nested map-pin entries |
| Onscreen strings | Four secondary keys for title, objectives, and pin |
| Subtitle map | One quest group entry pointing to the subtitle entries path |
| Subtitle entries | cqa005_subtitles.json; one unsigned string ID with identical male/female text |
| VO map | The same ID with identical male/female WEM paths |
| Streaming block | One finite Quest descriptor and one AlwaysLoaded descriptor |
| Quest sector | Setup trigger, cleanup trigger, AI spot, compiled community area |
| AlwaysLoaded sector | Scene marker, map marker, community registry |
| WEM | RIFF/WAVE binary at the VO-map path, not a CR2W resource |
If any owner is absent, restore the untouched checkpoint. Do not compensate by adding a guessed duplicate registration or by copying a base-game resource.
3. Verify the six registration roots
Open CQA_Lab05_FirstContact.archive.xl. Its registered paths are:
quest.phases cqa005.questphase
journal cqa005.journal
onscreens/en-us cqa005_onscreens.json
subtitles/en-us cqa005_subtitles_map.json
vomaps/en-us cqa005_vo.json
streaming.blocks cqa005_first_contact.streamingblock
Do not add the child, scene, subtitle entries, WEM, or sectors. Confirm how each is reached before proceeding:
| Indirect artifact | Native reference owner |
|---|---|
| Contact child | Root questPhaseNodeDefinition.phaseResource |
| Scene | Child questSceneNodeDefinition.sceneFile |
| Subtitle entries | Registered subtitle map entry |
| WEM | Registered VO-map line entry |
| Two sectors | Registered streaming-block descriptors |
4. Audit prefab and world ownership
Open the root phase and retain one phasePrefabs entry:
#cqa005_pr_first_contact
Open the child and retain phasePrefabs: []. Open root phase node 13 in the
start graph and retain phaseInstancePrefabs: [], phaseGraph: null, and the
soft path:
mod\cqa\cqa005\phases\cqa005_contact.questphase
Open the streaming block and inspect both descriptors:
- the Quest descriptor mounts the finite sector under
$/mod/cqa/cqa005/#cqa005_pr_first_contact; - the AlwaysLoaded descriptor reaches its sector independently.
Open the Quest sector. Match its four nodes to four nodeData placements and
four nested NodeRefs. The local tails are shown here as authoring shorthand:
#cqa005_tr_setup
#cqa005_tr_cleanup
#cqa005_spot_contact
#cqa005_com_contact
Open the AlwaysLoaded sector. Match the scene marker and map marker to string
NodeRefs. The third node is the registry, whose placement uses its numeric
world identity rather than a fabricated nodeRefs string entry.
5. Audit all community identity joins
Record these typed values separately:
| Domain | Canonical unsigned value | Derivation source |
|---|---|---|
| Community/source | 5948510988927765319 | $/mod/cqa/cqa005/#cqa005_pr_first_contact/#cqa005_com_contact |
| Registry node | 6908684691797323855 | Full community NodeRef above plus _registry |
| AI spot global ID | 15950783814303760596 | $/mod/cqa/cqa005/#cqa005_pr_first_contact/#cqa005_spot_contact |
These values use RED4’s alias-aware NodeRef hash. A generic text FNV tool that
includes the # marker will produce a different number.
Confirm the joins in WolvenKit:
- compiled-area
sourceObjectId.hashequals registry-itemcommunityId.entityId.hash; - the registry placement identity differs from that source identity;
- compiled-area
spotNodeIds[0]equalsworkspotsPersistentData[0].globalNodeId; - the registry template’s
spotNodeRefs[0]names the full placed AI spot; - source, registry, and spot IDs are nonzero and mutually distinct.
Then inspect the registry template:
entryName: contact
characterRecordId:
Character.jpn_tyger_claws_biker2_ranged2_copperhead_wa
entryActiveOnStart: 0
initialPhaseName / phaseName: default
appearance: default
quantity: 1
hour: Day
isSequence: 0
The compiled community area mirrors that template period. Under its one entry
and one phase, require entryName: contact, entryPhaseName: default, one
period with periodName: Day and isSequence: 0, and exactly one
spotNodeIds row whose hash is 15950783814303760596. Do not audit the
registry template alone: a mismatch in this compiled mirror breaks the
template-to-world join even when the three headline IDs are correct.
Inspect the AI spot’s AIActionSpot.resource:
base\workspots\common\ground\
generic__stand_ground_cigarette__smoke__01.workspot
Keep the supplied cigarette workspot while building the lab. If you change it, check the new pose, approach, and scene acquisition at the contact location.
6. Audit geometry before behavior
Retain the supplied placement and outline values:
| Node | Position | Outline |
|---|---|---|
| Setup | (-1000.02, 1497.2208, 2.3) | 16 points, 25 m circumradius, 12 m high |
| Cleanup | (-1000.02, 1497.2208, 0.3) | 20 points, 110 m circumradius, 16 m high |
| AI spot | (-1000.02, 1497.2208, 6.957) | yaw 88.6 degrees |
| Scene/map markers | (-1000.02, 1497.2208, 8.3) | yaw 88.6 degrees |
The setup node is worldTriggerAreaNode #cqa005_tr_setup; the cleanup node is
the separate #cqa005_tr_cleanup. Do not reuse one trigger for both waits.
The larger outer boundary is what makes delayed cleanup observable and
testable.
7. Audit journal and onscreen localization
Open the journal and confirm this typed tree:
quests/minor_quest/cqa005
└── cqa005_01
├── cqa005_01_obj_meet
│ └── cqa005_01_qmp_contact -> #cqa005_mp_contact
└── cqa005_01_obj_leave
Open the onscreen localization resource and retain:
| Secondary key | English value |
|---|---|
cqa_cqa005_title | First Contact |
cqa_cqa005_objective_meet | Meet the contact. |
cqa_cqa005_objective_leave | Leave the meeting area. |
cqa_cqa005_mappin_contact | First Contact |
The child changes objective and pin state. The root changes quest state. The localization resource owns only text.
8. Audit the spoken-line lookup
Open cqa005_subtitles.json, the indirect subtitle entries resource. Its root
data type is localizationPersistenceSubtitleEntries and it contains exactly
one entry:
stringId: 9638591835734011695
femaleVariant: All clear. Keep moving.
maleVariant: All clear. Keep moving.
Open the subtitle map. Its root data type is
localizationPersistenceSubtitleMap; its one quest group entry soft-refers
to cqa005_subtitles.json.
Open the VO map. Its root data type is locVoiceoverMap; its one
locVoLineEntry uses the same string ID and the same WEM path for
femaleResPath and maleResPath.
The source WAV is mono 48 kHz signed 16-bit PCM and lasts about 2.595 seconds. The prebuilt WEM is mono 48 kHz and lasts about 2.598 seconds. Keep its filename and VO-map path consistent when copying it.
9. Preserve the scene’s actor scaffold
Open cqa005_first_contact.scene. Keep scene version 5, cooking platform
PLATFORM_PC, and category minorQuests.
Actor 0 is contact:
- type
scnActorDef; - acquisition plan
community; - community reference
#cqa005_com_contact; - entry name
contact; - lipsync animation-set slot ID
0.
Player actor 1 is V:
- type
scnPlayerActorDef; - acquisition plan
findInContext; - record
Character.Player_Puppet_Base; - lipsync animation-set slot ID
0.
In debug symbols, retain performer ID 1 for the contact and 257 for V.
In resouresReferences—the serialized field is intentionally misspelled—keep
one scnLipsyncAnimSetSRRef pointing to:
base\animations\facial\generic\interactive_scene\
generic_facial_lipsync_gestures.anims
Retain the typed empty arrays, scnSRRefCollection, empty embedded
scnlocLocStoreEmbedded, debug symbols, and scene solution hash. Removing
apparently empty typed containers can change serialization; filling locStore
with the spoken line would also select the wrong localization system.
10. Preserve interruption and section safety policy
The default scnInterruptionScenario is ID 0, name Default, and enabled.
Its speaker-distance interrupt condition is Greater 6; its return condition
is Less 5. Retain playInterruptLine: 1 and talkOnReturn: 1.
This lab does not exercise active-line interruption/return or
CutDestination. Its test guide covers the ordinary lifecycle, the supplied
pre-scene seed loads, and post-contact_done plus completed reload; arbitrary
pre-scene states and broader interruption claims need their own observed
outcomes.
When you create the completed section, add actor behavior rows for actors 0
and 1, both OnlyIfAlive.
11. Add the screenplay line and event
In screenplayStore.lines, add one scnscreenplayDialogLine:
| Property | Exact value |
|---|---|
itemId | 1 |
speaker | actor 0 |
addressee | actor 1 |
locstringId.ruid | 9638591835734011695 |
usage.playerGenderMask.mask | 3 |
Leave male and female lipsync animation names at None. They are not WEM
paths.
In the new section’s events, add one scnDialogLineEvent:
| Property | Exact value |
|---|---|
| Event ID | 8646165628675208917 |
screenplayLineId | 1 |
startTime | 0 |
duration | 2598 ms |
| VO context | Vo_Context_Quest |
| VO expression | Vo_Expression_Spoken |
Set section duration to 2998 ms: line duration plus the deliberate 400 ms
tail. The locstring ID and event ID are separate unsigned identity domains.
12. Expand the exact scene graph
The start shell has Start 1 -> 3 End. Keep those IDs and add:
- Section node
2; scnQuestNode4, wrapping onequestPuppetAIManagerNodeDefinitionentry for the contact at AI tierCinematic.
Wire exactly three destinations:
| Source stamp | Destination stamp | Meaning |
|---|---|---|
Start 0/0 | Section 0/0 | Begin the line section |
Start 0/0 | PuppetAI 0/1 | Fire the parallel AI-tier operation |
Section 0/0 | End 0/0 | Terminate after the line section |
Retain these empty outputs:
- Section cancel output stamp
1/0has no destinations; - PuppetAI output stamp
0/0has no destinations; - End has
outputSockets: []andtype: Terminating.
Set startNodes to node 1 and endNodes to node 3. Keep entry start -> 1 and exit contact_done -> 3. The PuppetAI branch is deliberately
fire-and-forget; wiring it into End would create a different join policy.
Save and reopen the scene. Confirm the typed node IDs, socket stamps, screenplay item, event, and entry/exit remain unchanged.
13. Expand the contact child
Open cqa005_contact.questphase. Keep Input 0, terminating Output 1, and
their interface names In1 and Out1. Add nodes 10 through 22 from the
exact child table, then connect this one chain:
0.Out -> 10.Active -> 11.Active -> 12.In -> 13.In -> 14.In
-> 15.In -> 16.start
16.contact_done -> 17.Succeeded -> 18.Inactive -> 19.Active
-> 20.In -> 21.In -> 22.Succeeded -> 1.In
Where the shorthand crosses a node, use that node’s ordinary Out socket.
Do not connect Default INT, Default RET, or any CutDestination.
Set the decisive payloads:
- node
12: oneActivateaction for community#cqa005_com_contact, entrycontact, phasedefault; - node
13:questCharacterSpawned_ConditionType,Greater 0,entireCommunity: 1, same community reference; - node
14: playerIsInside #cqa005_tr_setup, withisPlayerActivator: 1; - node
15: checkpoint debug stringcqa005_first_contact, withendGameSave,ignoreSaveLocks,pointOfNoReturn,retryOnFailure, andsaveLockall0; - node
16: soft scene file,scnWorldMarker #cqa005_sm_contact,interruptionOperations: [], freeze/reapply/music flags0, and exactly the five sockets described above; - node
20: playerIsOutside #cqa005_tr_cleanup, also withisPlayerActivator: 1; - node
21: oneDeactivateaction with entry and phase bothNone, meaning the whole referenced community; - node
22: succeed the leave objective before returningOut1.
Both trigger conditions retain an explicit empty activatorRef; player
selection comes from isPlayerActivator, not from inventing an entity
reference. In each gameEntityReference, keep
dynamicEntityUniqueName: None, names: [], a uint64 NodeRef reference
of 0, sceneActorContextName: None, slotName: None, and type: EntityRef.
The journal nodes are not labels alone. Give each objective action the exact
gameJournalPath owner and payload below:
| Node | State | className | realPath |
|---|---|---|---|
10 | Active | gameJournalQuestObjective | quests/minor_quest/cqa005/cqa005_01/cqa005_01_obj_meet |
17 | Succeeded | gameJournalQuestObjective | quests/minor_quest/cqa005/cqa005_01/cqa005_01_obj_meet |
19 | Active | gameJournalQuestObjective | quests/minor_quest/cqa005/cqa005_01/cqa005_01_obj_leave |
22 | Succeeded | gameJournalQuestObjective | quests/minor_quest/cqa005/cqa005_01/cqa005_01_obj_leave |
On all four objective actions set optional: 0, sendNotification: 1,
trackQuest: 1, and version: Initial. Nodes 11 (Active) and 18
(Inactive) both target this map-pin path with className: gameJournalQuestMapPin and disablePreviousMappins: 0:
quests/minor_quest/cqa005/cqa005_01/cqa005_01_obj_meet/cqa005_01_qmp_contact
All six child gameJournalPath values also keep editorPath: "" and
fileEntryIndex: 2. These fields belong to the serialized path object; do not
omit them merely because realPath already looks unambiguous.
Keep child phasePrefabs empty.
14. Expand the registered root
The start root already contains Input 0, external child 13, and Output
1. Build the completed graph with the exact IDs in the overview. Node 12
becomes the external child in the completed candidate; IDs are resource-local,
so this does not change the child resource’s interface.
Wire:
0.Out -> 10.In
10.False -> 1.In
10.True -> 11.Active
11.Out -> 12.In1
12.Out1 -> 13.Succeeded
13.Out -> 14.In
14.Out -> 1.In
Node 10 compares cqa005_completed Equal 0. Node 14 sets it to exactly
1. Retain the root prefab, the child’s soft path, null inline graph, and
empty instance-prefab list.
Root journal node 11 sets the quest Active; node 13 sets the same quest
Succeeded. Both use className: gameJournalQuest, realPath: quests/minor_quest/cqa005, and the exact action flags optional: 0,
sendNotification: 1, trackQuest: 1, and version: Initial. Their two
gameJournalPath objects also use editorPath: "" and fileEntryIndex: 2.
15. Save, rebuild, and compare
- Save every changed resource.
- Close and reopen the project; inspect both phase graphs and the scene.
- Use WolvenKit’s project build/package action.
- Confirm the packed archive and loose
.archive.xlhave the project nameCQA_Lab05_FirstContact. - Confirm the package contains eleven CR2W depot paths plus the exact WEM.
- Confirm the child, scene, subtitle entries, WEM, and sectors are archived even though they are not direct ArchiveXL roots.
- Compare decisive properties with the unmodified completed checkpoint.
Successful saving, cooking, and packing prove serialization and packaging—not gameplay. Use Test First Contact with the unmodified completed checkpoint for the canonical runtime candidate.
Failure isolation before launch
| Symptom | Inspect |
|---|---|
| WolvenKit rejects a community sector | Handle ownership, typed IDs, nodeData count/index, source/registry/spot collision |
| Scene reopens with missing endpoints | startNodes[1], endNodes[3], entry/exit node IDs, End type |
| Line resource cooks but lookup is incomplete | Same unsigned ID in scene/subtitle/VO, map soft path, WEM depot path |
| External child looks valid but is unresolved | Child archived, root soft path, no extra root registration, matching In1/Out1 |
| Built game archive contains the WAV | Move authoring provenance outside project source; only the WEM belongs in the game archive |
| Start project invokes a line | Restore the start graphs; its phase shells must not invoke the scene shell |
Lab sequence: All labs · Previous: Lab 5: First Contact · Next: Test First Contact.
Test First Contact
This guide tests the complete First Contact checkpoint across ordinary play, reload, streaming, cleanup, replay, and removal. The eleven cases are deliberately separate: a scene working once does not show that its actor, journal state, audio, or cleanup will survive every other lifecycle boundary.
completed checkpoint
+ known starting saves
+ one controlled route per case
+ visible results and fresh logs
= a result you can diagnose and reproduce
| Record | Value |
|---|---|
| Structural validation date | 2026-08-09 |
| Runtime test date | Not yet recorded |
Use the unmodified completed checkpoint. A project edited while following the authoring chapter is useful for personal testing, but it is a different build and may fail for different reasons.
Required environment
| Component | Exact version |
|---|---|
| Cyberpunk 2077 for Windows (GOG) | 2.31a (public patch 2.31) |
| WolvenKit | 8.19.0 |
| ArchiveXL | 1.27.0 |
| RED4ext | 1.30.0 |
| redscript | 0.5.31 |
Record the versions you actually use. The WEM is already built; testing the lab does not require Wwise Console or audio conversion.
Freeze a minimal mod loadout
Run Cases 1–10 with the required frameworks and the completed Lab 5 pair.
Disable unrelated quest, world, community, AI, appearance, and framework mods
where practical. In particular, another mod can change the generic Tyger
record’s hostility, appearance, workspot behavior, or acquisition without
changing any cqa005 file.
Keep the loadout unchanged between cases. Case 11 is the exception: close the game and remove the Lab 5 pair so you can verify that the completed save does not depend on the mod remaining mounted.
Establish the site before installation
The shared center is the outdoor recycling-station cabinet row at Allen Street in Watson/Kabuki:
(-1000.02, 1497.2208, 8.3)
Before any Lab 5 candidate is installed, follow Lab 3’s ordinary control-route procedure. Confirm the route using the vanilla landmark rather than the Lab 5 pin that is under test. Do not replace an inaccessible route with a console teleport.
The two boundaries relevant to this campaign are:
| Boundary | Candidate geometry | Test role |
|---|---|---|
| Setup | 25 m circumradius, 16 points, 12 m high | Scene may advance only after spawned readiness and player entry |
| Cleanup | 110 m circumradius, 20 points, 16 m high | Community remains active until V is outside |
The radii describe the supplied polygonal prisms, not universal safe values. Record the actual route and first/last visible behavior instead of inferring a crossing from the numbers alone.
Verify the frozen spoken-line lookup
Before building, inspect the completed checkpoint and verify this exact lookup chain:
scene locstring RUID 9638591835734011695
-> registered subtitles\cqa005_subtitles_map.json
-> indirect subtitles\cqa005_subtitles.json
femaleVariant = maleVariant = "All clear. Keep moving."
-> registered vo\cqa005_vo.json
femaleResPath = maleResPath
-> vo\contact_i_85c3283507e7ef2f.wem
The complete depot paths are:
mod\cqa\cqa005\localization\en-us\onscreens\cqa005_onscreens.json
mod\cqa\cqa005\localization\en-us\subtitles\cqa005_subtitles_map.json
mod\cqa\cqa005\localization\en-us\subtitles\cqa005_subtitles.json
mod\cqa\cqa005\localization\en-us\vo\cqa005_vo.json
mod\cqa\cqa005\localization\en-us\vo\contact_i_85c3283507e7ef2f.wem
The subtitle map and VO map are registration roots. The subtitle entries and WEM are archived indirect dependencies. Renaming one file or registering the entries directly changes the candidate.
Use the supplied WEM unchanged for these tests.
Prepare test saves
Create and preserve two manual-save originals before the first Lab 5 installation. Never use an autosave or checkpoint save, and keep both source slots outside the game’s rotating autosave set.
- Make
CQA005 ORIGINAL OUTSIDE SETUPoutside the setup volume on the verified ordinary approach. - Make the separate
CQA005 ORIGINAL NEAR SETUPnear but still outside setup for the immediate-arrival race. Reach it through ordinary game travel, including the verified vanilla fast-travel approach if used; do not teleport. - Record each complete slot-directory name in your test notes.
- Confirm neither save has ever loaded any CQA tutorial candidate: no Lab 1–4
start/completed project and no Lab 5 start, completed, or older
cqa005candidate. Labs 3 and 4 share this site, and removing their archives does not erase saved quest, journal, trigger, or world state. If you already followed those labs, restore a separately preserved pre-tutorial save or create a genuinely new clean lineage; a disclosed prior-lab history is not sufficient. - Exit the game completely before cloning either complete source slot into a run-specific directory. Never execute a case from the immutable source directory itself.
- Case 1 must continue one unbroken ordinary route through completion and create exactly three additional manual-save captures at the frozen moments below. A checkpoint or autosave does not satisfy a capture row.
- After Case 1, exit the game completely before cloning any complete seed
slot for Cases 3–9 or 11. Never edit a clone, reset a fact, or copy only
sav.datwithout the rest of its slot directory.
The test uses five named save captures:
| Capture ID | Exact label | Source / parent | Frozen observable state | Execution cases |
|---|---|---|---|---|
original-outside-setup | CQA005 ORIGINAL OUTSIDE SETUP | External original / none | Outside setup on the ordinary route; never loaded any CQA Lab 1–5 candidate | 1, 10 |
original-near-setup | CQA005 ORIGINAL NEAR SETUP | Separate external original / none | Near but outside setup; never loaded any CQA Lab 1–5 candidate | 2 |
seed-pre-scene-outside-setup | CQA005 SEED PRE SCENE OUTSIDE SETUP | Case 1 / original-outside-setup | Meet Active; pin Active; child active; contact spawned and passive; V outside setup; checkpoint cqa005_first_contact not executed; scene not started; fact 0 | 3, 4, 7 |
seed-post-contact-inside-cleanup | CQA005 SEED POST CONTACT INSIDE CLEANUP | Case 1 / pre-scene seed | contact_done returned; Meet Succeeded; pin Inactive; Leave Active; child and contact active; V inside cleanup; fact 0 | 5, 6, 8 |
seed-completed | CQA005 SEED COMPLETED | Case 1 / post-contact seed | Leave and quest Succeeded; pin Inactive; contact deactivated; child and root returned; fact 1 | 9, 11 |
Keep all five source slots unchanged. With the game closed, copy the entire
appropriate slot into a separate working directory for each case, including its
metadata files. Check that its sav.dat matches the source copy. Never run a
case directly from the preserved source slot.
Cases 3, 4, and 7 use copies of the same pre-scene capture outside setup.
There is no save window between child checkpoint node 15 and scene node
16: they are connected directly. Cases 5, 6, and 8 share the post-contact
capture; Cases 9 and 11 share the completed capture; Cases 1 and 10 share the
outside original. Case 2 alone uses the separate near-site original.
Do not reset cqa005_completed to prepare a case. Journal, scene, community,
actor, phase, and streamed-world state can outlive that fact.
Build, install, and bind the canonical candidate
Use the unmodified completed checkpoint:
project: CQA_Lab05_FirstContact.cpmodproj
Open all eleven CR2W resources in WolvenKit before testing. Resolve missing files or conversion errors before installing the project.
-
Close the game.
-
Open the completed project in WolvenKit and use its normal build/package and install workflow.
-
Confirm the start checkpoint, every older
cqa005archive, and every Lab 1–4 tutorial candidate are absent. Labs 3 and 4 deliberately reuse this Allen Street test area; leaving them installed can add overlapping markers, triggers, objectives, and log traffic even though their depot namespaces differ. -
From the game directory, run:
Get-ChildItem .\archive\pc\mod\CQA_Lab0*.archive* | Select-Object -ExpandProperty NameThe complete output must be exactly these two names, with no Start variant and no
CQA_Lab01_*throughCQA_Lab04_*pair:CQA_Lab05_FirstContact.archive CQA_Lab05_FirstContact.archive.xl -
Confirm the candidate contains the twelve depot paths listed in the resource inventory, including the indirectly reached child, scene, subtitle entries, WEM, and both sectors.
-
Preserve byte-identical copies of both installed files until the complete campaign and log audit finish.
Keep the installed pair unchanged between cases. If you rebuild it, repeat the affected tests from their starting saves.
Capture every execution consistently
For each run, keep a dated note containing your game/framework versions, installed filenames, starting save label and slot directory, the source save it was copied from, and what you observed. Mark cases you did not run as pending. Keep a fresh copy of all four logs listed below before the next launch can overwrite them.
Use a separate working slot and log folder for every case. Reusing one run’s logs or observations cannot establish the result of another run.
Capture a short continuous video with game audio for timing, replay, visible spawn, scene, and cleanup claims. Add focused screenshots or notes for journal and pin states. Record the first and last visible contact behavior, the route, any combat stimulus, and whether a loading screen occurred. Do not use the video alone for log or save history.
Keep saves, logs, and captures in your own test folder. If you contribute a result to the book, follow the repository contribution guide for sanitizing and submitting evidence. Do not publish private saves or extracted vanilla resources.
Case 1: clean-ordinary-passive-spawn
Acceptance precondition: Install the canonical candidate, load a closed-game full-slot clone of the untouched outside-setup original, approach by ordinary movement, and continue the same unbroken route through ordinary completion while making the three named manual seed saves.
Required result: The root and child activate once, the passive community contact appears through the community/AI-spot mapping, and the meet objective and pin appear once without a spawn or NodeRef error. The ordinary route completes once, and exactly the pre-scene, post-contact, and completed seed captures are made at their frozen observable states.
Walk the verified route without fast travel, teleporting, combat, or waiting at the boundary to manipulate streaming. Begin capture before the first Lab 5 objective. Record the meet objective and pin appearing once, the contact at the authored spot, and the absence of hostile/combat behavior when unprovoked.
Continue this same run and make exactly these three manual saves, never a checkpoint or autosave:
CQA005 SEED PRE SCENE OUTSIDE SETUP, after the contact is spawned and passive but while V remains outside setup. Meet and pin are Active, the child is active, checkpointcqa005_first_contacthas not executed, the scene has not started, andcqa005_completedis0.CQA005 SEED POST CONTACT INSIDE CLEANUP, aftercontact_donereturns but before leaving cleanup. Meet is Succeeded, pin Inactive, Leave Active, child/contact active, and the fact remains0.CQA005 SEED COMPLETED, after cleanup and root return. Leave and quest are Succeeded, pin Inactive, contact deactivated, child/root returned, and the fact is1.
Keep the route capture, state screenshots, and fresh logs. Exit the game
before copying the three complete seed directories. There is no save window
between checkpoint node 15 and scene node 16.
Case 2: fast-arrival-race
Acceptance precondition: Load a closed-game full-slot clone of the separate untouched near-setup original and enter immediately before waiting for streaming or spawn.
Required result: The spawned-character wait resolves before the setup wait advances; the scene begins once only after the contact exists, with no missed trigger or deadlock.
This is the fast-travel/immediate-sprint setup-race case. Use the independently preserved near-site save, load it, and sprint into the 25 m setup volume as soon as player control returns. Do not pause nearby to let the actor appear first. If fast travel was used to prepare the pre-install route, record that fact and the vanilla destination; never replace it with a console teleport.
Capture from the loading screen through contact appearance and scene start. The decisive ordering is actor visible/available before the scene performs, one scene start, and eventual forward progress. A scene that never starts, a scene that starts without its contact, or a missed already-true setup condition fails the case even if a slower retry works.
Case 3: slow-contact-av-once
Acceptance precondition: Load a byte-identical full-slot clone of the named pre-scene outside-setup seed, approach slowly, remain in range for the full scene, and do not skip or interrupt it.
Required result: The exact subtitle All clear. Keep moving. and the
supplied WEM play once; the contact is acquired, the line does not duplicate,
and lipsync/audio errors are absent from retained logs.
Start from clean-derived-before-setup, a distinct execution-slot clone of
seed-pre-scene-outside-setup. Keep the
contact in view, enter at a walking pace, and remain until the scene completes.
Capture the exact subtitle, audible line, contact performer, and mouth/facial
response in one continuous recording. Count subtitle display and audible
playback separately; each must be exactly one.
Check the line with RUID 9638591835734011695. A subtitle without sound,
sound without the exact subtitle, or duplicate playback fails this test.
Case 4: named-exit-only
Acceptance precondition: Load a byte-identical full-slot clone of the same
named pre-scene outside-setup seed used by Cases 3 and 7, then observe ordinary
completion. This is not a save between checkpoint node 15 and scene node
16.
Required result: Only named exit contact_done advances the child to meet
Succeeded; Default INT/RET remain unwired and no fallback continuation is
observed.
Start from clean-derived-before-scene, another distinct execution-slot clone
of seed-pre-scene-outside-setup, not a new capture. Record the meet objective
before crossing setup, the scene’s ordinary terminating end, and the
transition to meet Succeeded/leave Active. Do not invoke an interrupt or cut
path in this case.
Pair the runtime capture with the canonical scene/questphase structural record
showing contact_done wired to child node 17.Succeeded and Default INT /
Default RET unwired. Runtime evidence must show no fallback or double
continuation; graph inspection alone cannot pass the case.
Case 5: no-replay-inside-lifetime
Acceptance precondition: Load a byte-identical full-slot clone of the named post-contact inside-cleanup seed, then cross the setup boundary or revisit the contact point before leaving cleanup.
Required result: The scene, subtitle, and WEM do not replay during the same child lifetime; the leave objective remains the only active progression state.
Use clean-derived-after-contact-inside-cleanup, a distinct execution-slot
clone of seed-post-contact-inside-cleanup. Stay inside the
110 m cleanup volume, and walk out of and back into the 25 m setup area or
revisit the contact point without crossing cleanup. Remain long enough that a
duplicate line would be obvious.
Capture the complete revisit and the journal state. The contact may remain as part of delayed cleanup, but the scene, subtitle, and WEM must not restart and the meet objective/pin must not reactivate.
Case 6: post-scene-reload
Acceptance precondition: Without changing the installation, load a byte-identical full-slot clone of the named post-contact inside-cleanup seed.
Required result: Meet stays succeeded, the pin stays inactive, leave stays active, the contact line does not replay, and crossing cleanup continues once.
Hash and load the contact-done-inside-cleanup execution clone, verifying it
matches seed-post-contact-inside-cleanup, with the exact installed pair
unchanged. Capture journal and pin state immediately after load and after
walking continuously outside cleanup. Record that the line does not replay
and that leave/child progression occurs once.
A run that repairs itself only after re-entering setup is a failure: the saved post-scene owner state must resume directly.
Case 7: stream-away-return
Acceptance precondition: Load a byte-identical full-slot clone of the named pre-scene outside-setup seed, travel by ordinary movement beyond the finite Quest descriptor box before entering setup, return, and finish.
Required result: The community and child remain coherent across streaming; returning resolves the same NodeRefs and advances the scene exactly once without duplicate contact entries.
Start from child-active-before-setup, a third distinct execution-slot clone
of seed-pre-scene-outside-setup. Do not enter
the setup volume. Travel by ordinary movement beyond the Quest descriptor’s
finite box, whose supplied X/Y range is -1300.02..-700.02 by
1197.2208..1797.2208, then return by the same recorded route. Do not use fast
travel for this case.
Capture departure, return, contact materialization, scene, and objective progression. Retain logs from both streaming transitions. Two contacts, a stale spot, unresolved NodeRefs, lost child state, or a second scene execution fails the case.
Case 8: cleanup-boundary-no-pop
Acceptance precondition: Load a byte-identical full-slot clone of the named post-contact inside-cleanup seed, then walk outward continuously through the cleanup boundary more than 110 metres from the shared center.
Required result: IsOutside resolves once, whole-community deactivation occurs only at cleanup, no visible contact pop occurs inside the cleanup volume, leave succeeds, and child Out1 returns once.
Load a third distinct contact-done-inside-cleanup execution clone and confirm
it matches seed-post-contact-inside-cleanup. Walk outward continuously; do
not fast travel, teleport, reload again, or double back. Keep the meeting point
in view when the route permits and record distance/route landmarks, the last
visible contact state, the leave-objective transition, and quest completion.
The actor must not visibly disappear while V is still inside the authored cleanup prism. The expected deactivation command and return are proved by the ordered progression and absence of errors, not by claiming that Spawn Manager guarantees same-frame visual removal.
Case 9: completed-reload
Acceptance precondition: With the same exact candidate installed, load a byte-identical full-slot clone of the named completed seed.
Required result: cqa005_completed bypasses the child; objectives, pin, community, scene, subtitle, and WEM do not reactivate or replay.
Load the completed execution clone, confirm it matches seed-completed, and
remain near or revisit the site long enough to observe the one-shot guard.
Record the absence of all six reactivation surfaces named in the required
result. A silent scene with a reactivated objective or actor still fails the
case.
Case 10: untouched-replay
Acceptance precondition: With the canonical candidate still installed, load a byte-identical full-slot clone of the same untouched outside-setup original used by Case 1 and repeat the ordinary route.
Required result: Spawn, acquisition, line, named exit, cleanup, child return, and root completion reproduce once with the same visible and logged behavior.
Load a working copy of the original untouched-preinstall-outside-setup slot
and repeat the complete route with the same build. Compare spawning, dialogue,
cleanup, and completion with Case 1.
This is clean reproducibility. Resetting cqa005_completed on the completed
save would test a different, dirty state and cannot pass this case.
Case 11: removal-isolation-diagnostic
Acceptance precondition: With the game closed, clone the named completed seed into the disposable execution slot, remove both exact candidate files before launching and loading that clone, and revisit the site without resetting any fact.
Required result: No mod-owned resource remains mounted, no Lab 5 objective
or pin reactivates, no cqa005 contact appears, fresh logs show no cqa005
registration, and the packaged fixture contains no TweakDB mutation or
override.
With the game closed, copy the complete seed-completed source slot to a
separate disposable slot. Preserve the installed pair, then move these two
files out of the game directory:
archive\pc\mod\CQA_Lab05_FirstContact.archive
archive\pc\mod\CQA_Lab05_FirstContact.archive.xl
Confirm there is no second Lab 5 archive or ArchiveXL file under another name.
Do not edit the clone, reset cqa005_completed, or use a save editor or console.
Archive the prior four logs, launch the disposable clone, revisit the site, and
then retain the fresh four-log bundle.
Confirm that the fresh ArchiveXL log contains no registration for
mod\cqa\cqa005, no Lab 5 objective or pin reactivates, and no mod-owned
contact appears. Completed journal history already stored in the save is not a
mount or reactivation and is not grounds for failure by itself.
The exact packaged inventory contains no r6\tweaks, YAML tweak, script, or
other TweakDB mutation source. Its
Character.jpn_tyger_claws_biker2_ranged2_copperhead_wa value is a read-only
characterRecordId reference. That source-level absence is structurally
validated; this diagnostic deliberately does not claim to prove every vanilla
field or exclude unrelated installed mods. Disable unrelated mods rather than
trying to edit or compare the generic record.
Label every artifact removal-isolation-diagnostic. Restore the unchanged
candidate pair only after the game and framework processes have fully exited.
Retain and audit four logs per execution
Before starting another run, copy these logs into that run’s test folder:
red4ext\plugins\ArchiveXL\ArchiveXL.log
red4ext\logs\red4ext.log
red4ext\logs\game.log
r6\logs\redscript_rCURRENT.log
Copy the logs before the next launch overwrites them. Keep them in a separate folder for each run.
Search all four logs for:
cqa005, all twelve depot paths, and the installed project name;#cqa005_pr_first_contact, the community, spot, scene marker, and trigger NodeRefs;- RUID
9638591835734011695andcontact_i_85c3283507e7ef2f.wemwhere the subsystem logs expose them; - ArchiveXL registration, missing resource, NodeRef, streaming, scene, community, localization, audio, RED4ext, and redscript errors.
Record both relevant matches and the absence of expected failures. Do not claim that silence proves A/V playback: Case 3 still requires direct subtitle and audio observation.
Failure triage
Preserve the failed run, save name, candidate pair, video, and all four logs before editing anything.
| Observation | Classification and first checks |
|---|---|
| Quest never appears | Failed registration/root activation; inspect exact pair, ArchiveXL log, root depot path, and untouched-save history |
| Meet/pin appears but no contact | Failed streaming/community join; inspect finite sector, source/registry/spot IDs, entry/phase, workspot, and NodeRef logs |
| Contact is hostile without provocation | Failed passive-contact case; retain surrounding stimuli and inspect the generic record/AI policy before changing the matrix |
| Immediate sprint deadlocks or scene starts without contact | Failed readiness/setup ordering; inspect child nodes 13 -> 14 -> 15 -> 16 and the fast run’s streaming logs |
| Exact subtitle appears without sound | Failed VO path/WEM lookup; inspect RUID, cqa005_vo.json, WEM depot path/hash, archive inventory, and audio logs |
| Sound plays without the exact subtitle | Failed subtitle-map chain; inspect registered map, indirect entries path, both variants, RUID, and localization logs |
| Line/subtitle plays twice | Failed exactly-once scene lifecycle; inspect duplicate registration, scene start, saved state, and setup replay |
| Scene ends but meet does not succeed | Failed named handoff; inspect contact_done, End 3, child output socket, and inert defaults |
| Reload replays the scene or pin | Failed save-backed owner transition; retain the derived save before inspecting scene/journal state |
| Return after streaming creates a second contact | Failed registry/area lifecycle or duplicate mount; inspect identities, descriptor ownership, and installed candidates |
| Contact disappears while V is inside cleanup | Failed cleanup timing/geometry; inspect trigger polarity, route, node 20, and deactivation order |
| Completed reload starts the child | Failed completion guard/write order or dirty candidate/save history |
| Removal run still mounts Lab 5 | Failed removal isolation; search for duplicate archive pairs and preserve fresh framework logs |
| Packaged project contains a tweak or script mutation | Reject the candidate; Lab 5 may reference the generic record but must not override or mutate it |
| A useful log was overwritten | Repeat the case from the same starting save and copy the fresh logs before another launch |
Lab sequence: All labs · Previous: Author First Contact in WolvenKit · Next: Troubleshooting.
Custom effects
Build an optics glitch that draws animated shapes over V’s view or above an NPC’s head, then fades them out when the status ends. The gameplay action, status duration, effect timeline, and rendered image have separate owners.
item, quickhack, or other gameplay action
-> status effect record
-> VFX entry: cqa_optics_glitch
-> target entity's named effect descriptor
-> worldEffect timeline (.effect)
-> particle system (.particle)
-> material and textures (.xbm)
Screen overlays and head-attached billboards using this chain are Runtime-proven. Build and test the two presentations separately before combining them with a quest.
Start with one presentation
| Result | Particle drawer | Entity registration | Placement |
|---|---|---|---|
| Shapes over V’s view | CParticleDrawerScreen | Player entity templates | Screen rendering; test first and third person separately |
| Shapes above an NPC | CParticleDrawerBillboard | The NPC’s actual entity template | A compatible slot such as Head, with a relative offset |
The effect name can be the same on both entities while their descriptors
select different .effect files. A player patch does not register the effect
on NPCs, and patching one generic NPC does not cover every custom character.
Tools and versions
Use the book’s toolchain, including
TweakXL 1.11.3 for the status and item records. Prepare original RGBA textures
in an image editor and import them with WolvenKit.
The particle and effect property examples were inspected with Cyberpunk 2.31
resources and WolvenKit 8.17.4. The book’s normal authoring target is
WolvenKit 8.19.0; reopen your resources and check their compiled data before
installing. Follow each page’s field paths rather than relying on editor tab
positions.
Author in this order
- Particles, textures, and materials: make one readable, transparent image before adding animation.
- Effect timelines: schedule the intro, loop, outro, and optional sound.
- Entity spawners and status effects: connect the effect name to the target entity and trigger it through a native status.
- Test and troubleshoot: check V, NPCs, expiry, cancellation, and reapplication independently.
Read Character records, entities, and appearances first if entity templates and component packages are unfamiliar.
Files in a small effect mod
These are example names for resources you author, not additional downloads:
| File | Owns |
|---|---|
mod\cqa\effects\optics_v.effect | V’s presentation timeline |
mod\cqa\effects\optics_npc.effect | NPC presentation timeline |
mod\cqa\effects\particles\v_intro.particle | Intro emitter and material for V |
mod\cqa\effects\textures\intro_d.xbm | Color image |
mod\cqa\effects\textures\intro_m.xbm | Independent opacity mask |
mod\cqa\effects\registration\optics_v.ent | Player spawner component |
mod\cqa\effects\registration\optics_npc.ent | NPC spawner component |
CQA_Effects.archive.xl | Entity patch targets and onscreen-text registration |
r6\tweaks\cqa_effects.yaml | Duration, VFX, status, action, and item records |
Archive the .effect, .particle, .xbm, and patch .ent resources at their
depot paths. Keep the ArchiveXL file and TweakXL YAML under
source\resources so they install as loose framework files.
Vanilla references
Extract references into a separate inspection project from your own game:
| Depot path | Inspect it for |
|---|---|
base\fx\player\status_effects\blindness\p_blindness.particle | Screen drawer, emitter modules, material, and compiled particle data |
base\fx\player\p_hacking_glitch\p_hacking_glitch_low.effect | A worldEffect resource and its track/event relationships |
engine\materials\particles_generic.remt | Textured particle material parameters |
base\characters\entities\player\player_wa_fpp.ent | Player components and effect-slot availability |
base\characters\entities\player\player_ma_fpp.ent | The other player body template’s components and slots |
These structures are Observed in vanilla. Keep the original extracted resources out of shared projects and downloads.
Particles, textures, and materials
A .particle resource decides how an image is emitted and drawn. Its material
decides how color, opacity, tint, and blending turn that image into pixels.
An effect can start correctly while its particles remain invisible, oversized,
or opaque.
Use the custom-effects toolchain. Begin with one static image and one presentation; add motion after it looks right in game.
Inspect the emitter
The focused resource shape is:
CParticleSystem
emitters[] -> CParticleEmitter
modules[]
CParticleInitializerLifeTime
CParticleInitializerSize
CParticleInitializerVelocity
CParticleModificatorAlphaOverLife
CParticleModificatorAlphaOverEffect
particleDrawer
material
localMaterialInstance -> CMaterialInstance
lods[]
emitterLoops
renderResourceBlob
Observed in vanilla: inspect this shape in
base\fx\player\status_effects\blindness\p_blindness.particle.
The emitter has both editable module values and compiled render data. They
must describe the same simulation.
| Property | Question to answer |
|---|---|
modules[].lifeTime | How long can a spawned particle live? |
modules[].size and scale | How large is the particle? |
particleDrawer | Is it a screen image or a world-space billboard? |
lods[].birthRate | Are more particles emitted over time? |
lods[].burstList[].spawnCount | How many spawn in a burst? |
emitterLoops | Can the emitter repeat? |
renderResourceBlob | Which compiled settings and updater data will rendering use? |
For a frame-based image, one burst of one particle avoids accumulating many copies of the same glyph. A finite emitter and a finite particle lifetime are separate settings.
Make a local texture-replacement test
- Extract the particle reference into your inspection project. Inspect its drawer, modules, compiled data, and material before changing anything.
- Make a local working copy at a unique mod depot path. Keep the original reference separate. For this first test, preserve its compiled size, lifetime, and emission settings.
- Draw a simple shape on a transparent RGBA canvas. Use a large, clear glyph before adding tiny details or fragment animation.
- Import the PNG through WolvenKit’s texture import workflow. Confirm that
the resulting
.xbmexists undersource\archiveat the intended path. Reopen or export it to check that the alpha channel survived. - Open the particle’s
localMaterialInstance. Inspect itsbaseMaterialand set the texture parameters to your imported assets. - Keep the drawer compatible with the presentation. First test V using the screen drawer; make the NPC variant separately.
- Connect the particle to an effect track, then register and trigger it.
- Check visibility, transparency, placement, and removal in game before changing the simulation or adding more frames.
This material-only adaptation is Experimental until checked in your game. Changing the texture does not give the particle a new size or lifetime.
Set material parameters deliberately
The textured route uses engine\materials\particles_generic.remt as both the
emitter material reference and the local material’s baseMaterial.
| Local material value | Purpose |
|---|---|
Diffuse | Color texture .xbm reference |
AlphaMask | Opacity-mask .xbm reference |
UseAlphaMask | Enables mask use in this material setup |
Color | Material tint applied to the texture |
ColorMultiplier | Color intensity; start conservatively |
AlphaGlobal | Overall opacity |
AdditiveAlphaBlend | Selects the material’s additive/alpha blend behavior |
SubUVWidth, SubUVHeight | Texture-sheet dimensions; use 1, 1 for a single image |
TexCoordSpeed | Texture-coordinate movement; keep zero for a static frame |
DistortAmount | Distortion amount; keep zero while checking the image |
Parameter names are case-sensitive. Add values with the parameter’s actual
RED type: texture references, Color, Float, or Vector4 as appropriate.
Reopen the cooked particle to check the material and referenced depot paths.
Tint and texture color multiply. A pink texture with a strong pink tint and high multiplier can lose its intended hue or look much brighter than the PNG. Start with a white tint when tuning a texture that already contains its final colors. Check against both bright and dark game backgrounds.
Keep opacity independent of dark color
An opacity mask describes coverage, not the artwork’s brightness. For an opaque black bow on a transparent background, its mask should be white inside the bow and black outside. Using the dark color image as its own mask can make the interior disappear.
Additive rendering adds light; it cannot supply a convincing opaque black interior. If one design mixes dark silhouettes and glowing hearts, give the layers separate materials:
| Layer | Color texture | Opacity | Starting blend choice |
|---|---|---|---|
| Glowing shapes | Colored glyphs | Independent coverage mask | Additive |
| Dark silhouettes | Dark artwork with visible edges | White inside the shape | Non-additive alpha |
The split-layer composition is Experimental. Check actual rendering; changing one blend parameter does not diagnose every faint or missing shape. Track alpha, texture alpha, the mask, material opacity, and the cooked material can each suppress the image.
Screen and billboard drawers need matching compiled settings
CParticleDrawerScreen draws the player presentation. The NPC variant uses
CParticleDrawerBillboard, with placement supplied by the entity descriptor.
Moving a screen particle to a head slot does not turn it into a billboard.
In the inspected particle layout, the screen variant’s compiled
renderResourceBlob.header.emitterInfo.vertexDrawerType is 1; the billboard
variant uses 0. Keep the authored drawer and compiled header consistent.
These values describe this layout, rather than every possible drawer type.
Check cooked size and lifetime
The compiled emitter header also contains its own loops, LOD emission data, and burst counts. Size and lifetime can additionally be baked into updater data. Saving a new module value is not enough if rendering still reads an old compiled value.
After changing simulation settings, reopen the cooked resource and inspect an export from it. If your authoring workflow does not regenerate the updater, keep the existing compatible simulation for the texture exercise. Do not reuse byte offsets from another particle or expect a JSON round trip to compile a new simulation.
For animated frame particles, prepare compatible finite-lived particles before adding the sequence. Match each particle’s lifetime to its frame interval and test that old frames do not remain visible underneath new ones.
Next: Effect timelines.
Effect timelines
A .effect resource schedules presentation. Its length is not the duration of
the gameplay status that requested it. A six-second status can keep a short
visual loop active, then let an outro play when the loop breaks.
Use the custom-effects toolchain and begin with one particle track before building an animated sequence.
Read the timeline resource
Observed in vanilla: inspect
base\fx\player\p_hacking_glitch\p_hacking_glitch_low.effect in WolvenKit.
The root type is worldEffect.
| Property or type | Owns |
|---|---|
worldEffect.length | Total authored timeline length, including the outro |
trackRoot → effectTrackGroup | Collection of tracks |
effectTrack.items | Handled timeline items |
worldEffect.events | References to the same item objects used by tracks |
effectTrackItemParticles | A timed particle resource invocation |
effectTrackItemLoopMarker | The timeline’s loop-marker item |
effectLoops[] → effectLoopData | Loop start/end times |
effectTrackItemSound | A timed audio event request |
Do not create unrelated copies of an event in the track and root event list.
They must reference the same object. Keep CR2W handles separate from the
event’s unsigned ruid.
Separate the three clocks
| Clock | Example | Setting |
|---|---|---|
| Gameplay duration | Six seconds of an optics status | TweakDB duration record |
| Presentation timeline | Intro, repeating motion, outro | .effect times and loop range |
| Particle lifetime | How long one spawned frame survives | Authored and compiled .particle simulation |
For a short animated effect, this is a useful starting layout:
timeline seconds: 0 0.2 1.0 1.6
| intro | repeating motion | outro |
+-----------------------+
loop range
status active -> presentation repeats the loop
normal stop -> break loop -> finite outro -> finished
The intro is outside the loop. The outro and any one-shot exit sound are also
outside it. Setting worldEffect.length to six seconds would instead change
the presentation layout; it would not configure a six-second gameplay status.
Author one particle track in WolvenKit
- Create or open a mod-owned
.effectwith aworldEffectroot and PC cooking target. Inspect the reference’s group, track, and event types. - Add an
effectTrackbeneath the root group. Add a handledeffectTrackItemParticlesto itsitemsarray. - Set
particleSystemto the exact archived.particledepot path. - Set
timeBeginandtimeDuration. These timeline values use seconds; do not copy millisecond values from a scene dialog event. - Give the item a unique
ruid. Add the matching object reference to the rooteventsarray rather than constructing another item with the same ID. - Inspect
alpha,emissionScale,size, andvelocity. The constant-value setup useseffectEffectParameterEvaluatorFloatwith aCEvaluatorFloatConstevaluator of1.0for each. A zero alpha or emission scale can hide otherwise valid particles. - Set the root length to cover the item, save, and reopen it. Check the track, item reference, particle path, evaluators, and RUID.
- Bind the event to a placement in the entity descriptor.
For the first game check, use a single visible track and verify removal. A timeline item ending does not establish that an incompatible long-lived particle disappears with it.
Add the loop and outro
- Put intro tracks before
0.2seconds. - Add a loop marker beginning at
0.2, lasting0.8seconds. - Set the corresponding
effectLoopData.startTimeto0.2andendTimeto1.0. Keep the marker and loop table consistent. - Place the motion tracks entirely within that interval.
- Put finite outro tracks at or after
1.0. Set the root length to the latest outro event’s end. - Test normal expiry, early removal, and reapplication. Normal loop break and an immediate effect stop can produce different cleanup behavior.
Loop-break, immediate-stop, and overlapping reapplication behavior are Experimental for a new effect. Design the gameplay owner to use the intended stop route, then test that route explicitly.
Animate images with tracks
Motion can be drawn into a sequence of original texture frames. Each frame has a particle resource, and the effect timeline selects them in order. This is texture animation; scattered pixels drawn into successive frames are not independent physics particles.
For eight motion frames over 0.8 seconds:
| Frame | Begin | Duration | End |
|---|---|---|---|
hold_00 | 0.2 | 0.1 | 0.3 |
hold_01 | 0.3 | 0.1 | 0.4 |
| … | … | … | … |
hold_07 | 0.9 | 0.1 | 1.0 |
The intro can occupy 0.0–0.2. Twelve outro frames at 0.05 seconds each
occupy 1.0–1.6, so the root length is 1.6.
Prepare the compiled particle lifetime
for each interval. Timing a 0.1-second track does not rewrite a particle’s
baked lifetime. Check for gaps, overlapping old frames, and a visible jump at
the last-to-first loop boundary.
For separate opaque and glowing layers, give each layer its own particle track at the same frame times. Use separate material instances; a shared material cannot give each layer independent blend settings.
Put a one-shot sound after the loop
Add an effectTrackItemSound after the loop end. Set its eventName to an
available game audio event, and keep its positionName consistent with the
effect’s placement setup. ui_fx_hacking_glitch_fullscreen_low is an example
of an existing electronic event name; it is not a WEM depot path.
For a loop ending at 1.0, a sound beginning at 1.005 stays outside the
repeating interval. Give the sound its own RUID and compiled placement row.
Listen in game: a valid event name alone does not establish audibility,
spatialization, or one-time playback. Audio behavior here is Experimental.
Next: Entity spawners and status effects.
Entity spawners and status effects
The status system requests an effect by name. The target entity must have an effect descriptor with that same name, pointing to your timeline. A successful status application can still produce no custom visual if that descriptor is missing.
Use the custom-effects toolchain, including TweakXL for the records below. Restart the game after changing records or entity patches.
Keep the names and paths separate
| Domain | Example | Must match |
|---|---|---|
| TweakDB VFX record | CQAEffects.FX | The status’s VFX array |
| Effect CName | cqa_optics_glitch | StatusEffectFX.name and entEffectDesc.effectName |
| Component CName | cqa_optics_fx | Your independently named spawner component |
| Effect resource path | mod\cqa\effects\optics_npc.effect | Descriptor’s effect reference |
| Placement tag | Head | A compatible slot on the target entity |
| Timeline event RUID | An unsigned 64-bit value | The matching compiled eventRUID |
The component name is not the effect name. The effect name is not a depot path. Changing one does not rename the other fields.
Add one named spawner
For a character entity you own, add the spawner to that entity’s compiled
component package in WolvenKit. For patching an existing entity, create a
small mod-owned patch .ent containing an entity root and your spawner:
entEntityTemplate
compiledData -> RedPackage
Chunks
entEntity
entEffectSpawnerComponent
name: cqa_optics_fx
id: <unique component CRUID>
effectDescs[] -> entEffectDesc
id: <unique descriptor CRUID>
effectName: cqa_optics_glitch
effect: mod\cqa\effects\optics_npc.effect
compiledEffectInfo: worldCompiledEffectInfo
Generate new component and descriptor IDs in WolvenKit. Keep the package’s
component-ID entries consistent when adding chunks. Add the effect path to
the template’s resolvedDependencies where required by its resource shape.
Use a new component name. Replacing the target’s existing status-effect spawner can remove unrelated game effects. The patch entity should contain your additions, not a complete copied player or NPC template.
Bind events to a placement
The descriptor’s compiledEffectInfo joins the timeline’s events to a target
slot and relative transform:
placementTags[0]: Head
relativePositions[0]: (0, 0, 0.18)
relativeRotations[0]: identity quaternion
placementInfos[0]
placementTagIndex: 0
relativePositionIndex: 0
relativeRotationIndex: 0
eventsSortedByRUID[]
eventRUID: <same ruid as the timeline item>
placementIndexMask: 1
For this single-placement layout, mask 1 selects the first placement. Each
particle or sound item gets a matching event row; the loop marker does not
need one. Sort rows by the full unsigned numeric RUID. Preserve compatible
native flags when adapting the placement structure.
0.18 is an example offset above the NPC’s head slot. Tune it on the actual
character. Check the slot’s binding to its bone in the entity’s slot component;
writing Head in a descriptor does not create that slot.
After adding or deleting a timeline event, update this table too. A valid
.effect with stale placement rows can fail at a different point from a
missing particle or material.
Patch the actual entity templates
ArchiveXL maps a patch resource to the templates receiving it:
resource:
patch:
mod\cqa\effects\registration\optics_v.ent:
- base\characters\entities\player\player_ma_fpp.ent
- base\characters\entities\player\player_wa_fpp.ent
- base\characters\entities\player\player_ma_tpp.ent
- base\characters\entities\player\player_wa_tpp.ent
This list covers the named player templates. Add the appropriate cutscene, reflection, or Phantom Liberty templates when those views are part of your supported behavior. Do not infer coverage from a single first-person test.
For NPCs, map optics_npc.ent to the exact entity template used by the
character record. Add a custom character’s own depot path explicitly; a list
of vanilla gang templates does not cover it. Start with one supported target
before expanding to more body types or factions.
The .effect, particles, and textures are reached through native references.
They do not need separate quest-root registrations.
Define the status records
The following TweakXL YAML gives a derived visual status a six-second duration and requests the named spawner effect:
CQAEffects.DurationValue:
$type: ConstantStatModifier
modifierType: Additive
statType: BaseStats.MaxDuration
value: 6.0
CQAEffects.Duration:
$type: StatModifierGroup
statModsLimit: -1
statModifiers: [CQAEffects.DurationValue]
CQAEffects.FX:
$type: StatusEffectFX
name: cqa_optics_glitch
shouldReapply: false
BaseStatusEffect.CQAOpticsVision:
$base: BaseStatusEffect.QuickHackBlind
duration: CQAEffects.Duration
VFX: [CQAEffects.FX]
SFX: []
BaseStatusEffect.CQAOpticsBlind:
$base: BaseStatusEffect.ShortBlind
duration: CQAEffects.Duration
Use the exact field names VFX and SFX. Lowercase vfx or sfx does not
override them. This can leave the inherited white blindness presentation in
place even though the custom item and status exist.
The two status records have different responsibilities: the visual status
requests your effect, while the short-blind status supplies the paired native
gameplay effect. Do not apply the same pair twice from different routes.
shouldReapply: false is a starting policy, not a guarantee about overlapping
status applications; check reapplication in game.
Trigger a player test through an inventory action
Use Instigator for a self-test. Target is appropriate for the recipient of
a targeted action such as a quickhack.
CQAEffects.ApplySelfVision:
$type: ObjectActionEffect
recipient: ObjectActionReference.Instigator
statusEffect: BaseStatusEffect.CQAOpticsVision
CQAEffects.ApplySelfBlind:
$type: ObjectActionEffect
recipient: ObjectActionReference.Instigator
statusEffect: BaseStatusEffect.CQAOpticsBlind
CQAEffects.SelfTestAction:
$base: ItemAction.Eat
completionEffects:
- CQAEffects.ApplySelfBlind
- CQAEffects.ApplySelfVision
Items.CQAOpticsSelfTest:
$base: Items.LowQualityFood1
displayName: cqa_optics_selftest_name
localizedDescription: cqa_optics_selftest_description
objectActions: [CQAEffects.SelfTestAction, ItemAction.Drop]
itemSecondaryAction: CQAEffects.SelfTestAction
OnEquip: []
statModifierGroups: []
statModifiers: []
OnEquip is also case-sensitive. Clearing the derived food item’s stat
modifiers avoids inheriting unrelated food bonuses in this test item.
Create the two onscreen text keys and register their resource using Localization paths. To make the item available through a native vendor, append your own stock record:
CQAEffects.SelfTestStock:
$type: VendorItem
item: Items.CQAOpticsSelfTest
quantity: [Vendors.Always_Present]
Vendors.wat_lch_netrunner_01:
itemStock:
- !append-once CQAEffects.SelfTestStock
Vendor availability and refresh are Experimental for a new item. Check on a suitable clean save; a previously generated inventory may retain old stock.
Connect a targeted quickhack
- Inspect
QuickHack.BlindHack,Items.BlindProgram, and their inherited action effects in the installed TweakDB. - Derive new item and action records under your own namespace.
- Create
ObjectActionEffectrecords usingObjectActionReference.Targetfor the two custom statuses. - In the derived action’s
completionEffects, replace the original blindness payloads and deliberately handle any spread payload. Preserve unrelated upload and control effects. - Point the derived item’s
objectActionsat your action. Give its UI fields matching localized keys. - Equip the item and use it through the scanner on the NPC whose entity you patched. Check both status behavior and the rendered effect.
Remove inherited entries by their exact record IDs from your installed build. Do not use an inline record number as a reusable base or copy an array index without inspecting the effect it selects.
Next: Test and troubleshoot.
Test and troubleshoot custom effects
Test the visual on V and on a supported NPC before integrating it with a quest. A working item, a successful status application, and a visible effect are three different checkpoints.
Use the custom-effects toolchain. Close the game before replacing archives, entity patches, or TweakXL records, then restart. Use a disposable save made before installing the item or changing its vendor stock. Keep a separate save for checking behavior after expiry and reload.
Check the resource chain before launch
- Reopen the cooked
.effect, particles, and patch entities in WolvenKit. - Compare the status’s VFX name with the descriptor’s
effectNameexactly. - Check that the patch targets the entity template the character actually uses, and that the patch adds your independently named spawner.
- Match each particle and sound RUID to its compiled placement row.
- Check the loop range, finite outro, root length, and sound position.
- Check particle drawers, compiled simulation settings, materials, texture paths, and opacity masks.
- Run WolvenKit’s project validation and install normally. Confirm the
archive,
.archive.xl, and TweakXL YAML reached their game directories.
Start with normal expiry
Consume the native self-test item on V. Watch the entire sequence:
status starts -> intro -> repeating motion -> status expires -> outro -> clear view
Check that transparent regions reveal the game, the shapes fit the screen, the loop has no obvious jump, and old frames do not accumulate. Repeat against bright and dark backgrounds and with both supported player body templates.
Use the targeted quickhack on the supported NPC. Check the same lifecycle, then move around the character: the billboard should follow the intended slot without sitting inside the head or remaining at the spawn point.
Exercise cleanup separately
New cancellation, sound, split-material, and reapplication behavior is Experimental until exercised in game.
| Test | What to watch |
|---|---|
| Normal expiry | Status ends; outro finishes; no particles remain |
| Early status removal through your gameplay route | Visual leaves by the intended outro or immediate-stop policy |
| Reapply while active | No uncontrolled layering, unexpected restart, or duplicated exit sound |
| Reapply after expiry | A complete fresh sequence starts and ends once |
| Target dies or leaves streaming range | No floating image or sound remains without its owner |
| Change first/third-person view | Supported player templates resolve the correct presentation |
| Reload after expiry | No restored blindness, stranded overlay, or unexpected replay |
| Save during the active status, if permitted | Loaded state behaves coherently; record whether the timer resumes or restarts |
| Enable another mod affecting the same character | The custom spawner and the character’s existing effects coexist |
Listen to the game while testing the sound. A silent video or an authored frame preview cannot tell you whether an audio event played.
Diagnose the first failing link
| Symptom | Inspect first |
|---|---|
| Item has no name or description | Onscreen keys, localization resource, ArchiveXL registration |
| Item exists but produces no status | Item action, completionEffects, recipient, status record IDs |
| Blindness works but V shows a white screen | Exact VFX/SFX casing, derived status, custom name, player spawner patch |
| V works but an NPC has no custom visual | The NPC’s real entity path, separate NPC patch, descriptor name |
| Vanilla NPC works but custom character does not | Explicit patch target for the custom .ent and compatible slot bindings |
| Status starts but neither variant renders | Effect-name join, patch loading, descriptor path, event placement rows |
| Sound or particles appear at the wrong place | Placement tag, slot binding, relative transform, RUID-to-placement join |
| Image is much too large despite a size edit | Compiled particle size and screen/billboard drawer settings |
| Old frames remain visible | Baked lifetime, emission rate, burst count, emitter loops, overlapping intervals |
| Shapes are invisible | Track alpha/emission scale, material opacity, imported alpha channel, texture paths |
| Dark shapes look like faint outlines | Independent coverage mask, blend choice, material layer, background contrast |
| Pink looks red or excessively bright | Texture color multiplied by tint and ColorMultiplier, then in-game exposure |
| Sound repeats on every motion cycle | Sound event placed inside the loop |
| Outro never appears | Loop-break route, loop boundaries, outro timing, root length, immediate-stop behavior |
| Effect persists after the status | Particle lifetime, surviving emitter, stop policy, other active status applications |
Inspect ArchiveXL’s log under red4ext\plugins\ArchiveXL and TweakXL’s log
under red4ext\plugins\TweakXL for missing targets, unresolved paths, or
unknown record properties. If the template does not receive the spawner,
retuning the texture will not fix it.
Back to: Custom effects.
Gameplay patterns
This cookbook composes the native quest, journal, scene, community, world, device, character, and vehicle systems taught earlier. Start from the player behavior you want, then follow the resource owners all the way through activation, a durable completion signal, and cleanup.
These are composition guides rather than drop-in templates. Use the tested version set, and expect to adapt identities, world resources, journal paths, and cleanup to your own quest.
Choose the owning systems
| Desired behavior | Primary guide | Systems that must agree |
|---|---|---|
| Send a text, offer a reply, or start a call | Messages, calls, and conversations | Journal, localization, phone state, and optionally scene/audio |
| Turn a choice into later content | Branching, choices, and debriefs | Choice owner, facts, gates, journal, and downstream phases |
| Reach, leave, interact, hack, plant, or deposit | Areas, devices, and hacking | World placement, NodeRefs, controller state, quest logic, and save identity |
| Acquire an item, read a shard/file, or scan a clue | Items, shards, files, and scans | TweakDB or journal content, localization, inventory/scan producer, and quest observer |
| Place an actor or expose a device interaction | Workspots and interactions | Community or entity template, world placement, workspot, controller, and quest lifecycle |
| Open, close, lock, or expose a world door | Doors are device contracts | Door entity/controller, NodeRef, supported action, interaction policy, navigation, and persistent state |
| Wait in real time, game time, or for a time period | Delays, facts, and persistence | Clock domain, wait semantics, durable facts, reload, and re-entry policy |
| Meet or coordinate a named NPC | NPC interaction and meetings | Community identity, readiness, acquisition, scene or interaction, and cleanup |
| Monitor stealth, run combat, plant, or destroy | Stealth, combat, and destruction | Parallel monitors, encounter owner, device/target capability, outcomes, and cleanup |
| Show a custom optics overlay or an NPC-attached visual | Custom effects | Status records, entity spawner, effect timeline, particles, materials, and textures |
| Release, escort, defend, carry, or place an NPC | Rescue, escort, defend, and carry | Actor ownership, AI roles, ordered volumes, combat/carry state, and terminal cleanup |
| Mount, ride in, drive, or steal a vehicle | Mount, ride, drive, and theft | Vehicle identity, seat roles, mount conditions, route volumes, and lifecycle policy |
| Deliver, remove, chase with, or race a vehicle | Vehicle delivery, cleanup, chase, and race | Vehicle state, trigger/speed observations, AI route or race systems, restrictions, and cleanup |
| Resolve an outcome, debrief, reward, and finish | Rewards, switches, and outcomes | Durable outcome, switch/branch, presentation, reward record, journal state, and termination |
These guides deliberately overlap at system boundaries. A plant objective, for example, uses both the device recipe and the stealth/combat lifecycle; a vehicle delivery may finish through the outcome/reward recipe. Follow both guides instead of treating either page as a complete quest template.
What each recipe supplies
Each page supplies:
- a resource and ownership checklist;
- a conceptual flow and the decisive native node or condition shapes;
- a manual WolvenKit composition order;
- named vanilla depot paths to extract from your own installation;
- clean-save, reload, interruption, and cleanup checks appropriate to the pattern;
- failure isolation and links back to the foundational chapters.
It does not supply a universal drop-in graph, generate missing assets, or make one vanilla node portable outside its original identities. The pages do not redistribute extracted CR2Ws. Downloadable Labs 1–5 remain the executable incremental reference projects; these broader recipes explain patterns whose new combinations usually still require their own runtime campaign.
Read in dependency order
For a multi-system quest, a reliable order is:
- define durable facts, journal states, outcomes, and one-shot policy;
- inventory every world, device, community, character, scene, item, and vehicle owner;
- prove placement, NodeRefs, streaming bounds, identity joins, and readiness;
- compose the smallest activity path and its authoritative completion signal;
- add optional monitors, branching, presentation, rewards, and cleanup;
- test each branch on an untouched save, then repeat save/load, stream return, interruption, failure, removal, and replay cases that can expose persistence.
Do not begin with presentation polish. A subtitle, prompt, mappin, or objective can appear while the producing device, actor, vehicle, or condition is still wrong.
Clean-save rule
Facts, journal entries, scenes, communities, device persistent state, and some world or vehicle state can survive ordinary reloads. When a test changes any of those identities, close the game, install the exact candidate, and begin from a save created before that candidate was installed or streamed. Keep that save untouched, record the versions and build you tested, and write down what you expected and what actually happened. Resetting one fact is not a clean retest when other quest systems have already written state into the save.
Messages, calls, and conversations
Use a journal thread for text, a phone operation for call state, and a scene for performed dialogue. These systems can appear in the same player-facing conversation, but they have different resource owners and completion signals.
Tested with Cyberpunk 2077 2.31a, WolvenKit 8.19.0, ArchiveXL 1.27.0,
RED4ext 1.30.0, and redscript 0.5.31. See Tested
versions.
This page composes the native pieces taught in Messages, files, emails, and onscreens. It does not require a generator or a manifest compiler, and it does not turn a voiced holocall into a text-message recipe.
Choose the presentation owner first
| Player experience | Primary owner | Questphase responsibility | Additional assets |
|---|---|---|---|
| One-way text | gameJournalPhoneMessage beneath a contact conversation | Activate the message in the intended order | Journal contribution and onscreen localization |
| Text offer or reply menu | gameJournalPhoneChoiceGroup and gameJournalPhoneChoiceEntry | Activate the group, observe the winning entry, and persist the result | Same journal and localization owners |
| Audio call state | questPhoneManagerNodeDefinition | Start or end a call for two journal contacts | Both contact paths; phone restrictions must be deliberate |
| Voiced holocall | .scene launched by questSceneNodeDefinition | Coordinate the call and consume a named scene result | Scene, actor/context bindings, subtitles, VO map, WEMs, and any lipsync resources |
| In-person conversation | .scene plus a ready actor/community | Acquire the actor, start the scene, consume its named exit, then clean up | Community, AI spot, placement marker, scene, localization, audio |
A journal contact can own text messages without being a complete callable contact. Adding a contact, starting a call, playing a scene, and activating a message are separate operations. Keep those boundaries visible in the graph.
Shared prerequisites
Before wiring any recipe on this page, create and inspect:
- a mod-owned
.journalcontribution containing the contact, conversation, messages, choice groups, and choice entries used by the route; - globally unique onscreen localization keys for every contact name, thread title, message, and choice label;
- ArchiveXL registration for the journal and onscreen localization resources;
- a mod-owned
.questphasewith explained input, output, and cut sockets; - separate facts for presentation sent, player response, and activity completion when those meanings differ;
- an untouched save created before the custom journal identities were installed.
For a voiced route, also finish the prerequisites in
Scene resource anatomy,
Actors and performers, and
Entry, exit, and quest handoff.
A .scene reference does not synthesize actors, subtitle maps, audio, or phone
state.
Recipe: send an ordered text sequence
The smallest sequence activates one journal entry at a time:
In1
-> sent == 0?
False -> Out1
True -> message 1 Active
-> message 2 Active
-> set sent = 1
-> Out1
Use questJournalNodeDefinition with a
questJournalEntry_NodeType for each message. The decisive fields are:
| Field | Required meaning |
|---|---|
type.path.className | gameJournalPhoneMessage |
type.path.realPath | Full path to exactly one message |
type.path.fileEntryIndex | Index of the containing contact in the path; commonly 1, but calculate it |
type.sendNotification | Whether this activation should request phone presentation |
| incoming socket | Active |
| outgoing socket | Out means the activation request continued, not that the message was read |
The message entry’s own delay participates in phone presentation. A graph
questRealtimeDelay_ConditionType is a different owner: it delays execution.
Do not add an arbitrary graph delay and then describe it as proof that the
player saw the earlier message.
Protect a one-shot sequence with a persistent fact and an already-sent bypass. Write the fact at the boundary whose meaning it names. If it means “the opening messages were requested,” write it after their activation. If it means “the player acknowledged the exchange,” write it only after the acknowledgement gate.
For an offer that follows another quest, place the prerequisite wait and any game-time delay before both message and choice activation. Follow the completion-then-delay example. Adding a gate to a later build does not remove an offer already stored in a save’s journal.
Observed in vanilla: the compact thread at
contacts/dex/q003_flathead_price inside
base\journal\cooked_journal.journal contains a message, a one-choice group,
and a following message with the expected entry properties. It proves the
journal hierarchy, not a new mod-owned questphase.
Custom message ordering remains Experimental. Test first delivery, each reply, reload, and duplicate prevention from the specified starting saves.
Recipe: make a one-choice text job offer
A one-choice offer uses this native shape:
In1
-> opening message Active
-> accept-choice group Active
-> set offer_sent = 1
-> wait until the accept choice is visited
-> set offer_accepted = 1
-> Out1
| Step | Native node and decisive payload |
|---|---|
| 1–2 | questJournalNodeDefinition → questJournalEntry_NodeType, entered through Active |
| 3 and 5 | questFactsDBManagerNodeDefinition → questSetVar_NodeType, setExactValue: 1, value: 1 |
| 4 | questPauseConditionNodeDefinition → questJournalCondition → questJournalEntryVisited_ConditionType |
| 6 | Normal external child output such as Out1 |
The wait path must address the individual
gameJournalPhoneChoiceEntry, not its group. The exact retained one-choice
offer used a visited condition. The multi-choice conversations below instead
route on each entry’s Succeeded state. Both shapes exist in the evidence;
do not assume that Active, visited, and Succeeded are interchangeable.
The accepted fact is a handoff, not the whole quest. A following phase should activate the quest, objective, description, and mappin that the offer promises. See Journal state and tracking. Keep the quest inactive if the design intentionally allows a declined offer, and give that route an explicit output instead of leaving an active listener behind.
Test that the offer appears, accepting it activates the intended objective, and declining it leaves no active listener. Repeat after reload.
Recipe: route a two-choice text conversation
Use one wait per choice, one reply per branch, and an explicit convergence:
common message(s) Active
-> choice group Active
+-> wait choice A Succeeded -> reply A Active -> set outcome_a = 1 --+
`-> wait choice B Succeeded -> reply B Active -> set outcome_b = 1 --+
|
two-input XOR-shaped convergence <-------+
-> final message Active
-> acknowledgement gate
-> Out1
| Concern | Native representation |
|---|---|
| Winning choice | questJournalEntryState_ConditionType, state: Succeeded, individual gameJournalPhoneChoiceEntry path |
| Branch response | One questJournalNodeDefinition entered through Active |
| Durable outcome | One branch-specific fact writer before convergence |
| Convergence | Two-input questLogicalXorNodeDefinition, with both inputs and Out1 inspected |
| Required acknowledgement | questJournalEntryVisited_ConditionType, visited: 1, final message path |
The branch facts must be mutually exclusive. The retained XOR-shaped route does not establish that a logical XOR cancels a losing wait. If both choice facts can become true, a later signal may reach supposedly finished logic; that behavior is Experimental. Persist the winner at the choice boundary, and make a late losing signal harmless or explicitly cut it.
Test each selected-state wait separately. Confirm the matching reply, result fact, and convergence, then reload before and after the response.
Text is not a holocall
A performed call adds both phone lifecycle and scene lifecycle. Two vanilla references make the boundary concrete:
- Observed in vanilla:
base\quest\side_quests\sq004\phases\sq004_03_raffen_shiv_camp.questphasecontainsquestPhoneManagerNodeDefinitionoperations whose nestedquestCallContact_NodeTypeusescaller,addressee,mode: Audio, and distinctStartCallorEndCallphases. - Observed in vanilla:
base\quest\side_quests\sq011\phases\sq011_concert.questphaselaunchesbase\quest\side_quests\sq011\scenes\sq011_09_nancy_call.scenethrough aquestSceneNodeDefinition. The retained scene node has a softsceneFile, a taggedscnWorldMarker, named scene inputs/outputs,Holocall INT,Holocall RET, andPrefetch. A nearbyquestPhone_ConditionTyperecords caller, addressee, andcallPhase: IncomingCall.
Those are focused resource observations, not a universal wiring diagram. A safe planning shape is:
phone availability/restriction gate
-> StartCall operation
-> questSceneNodeDefinition named input
-> consume the intended named scene result
-> EndCall operation
-> persist result and continue
| Owner | What must be authored and tested |
|---|---|
| Phone operation | Correct caller/addressee gameJournalContact paths, mode, phase, restriction, rejectability, avatar/visual settings |
| Scene node | Exact .scene depot path, location/context, input socket, every completion/rejection/interruption output |
| Scene | Actors, screenplay, sections, events, named exits, and cleanup |
| Localization/audio | Spoken-line IDs, subtitle entries and map, VO map, WEMs, lipsync slot/resource contract |
| Quest | What rejection, interruption, reload, and an already-busy phone do |
No book-owned custom holocall has a retained pinned-baseline acceptance run. The assembled custom-call recipe is therefore Experimental. Start with a text thread or the one-line in-person scene unless the quest genuinely needs phone performance. For the scene half, follow Author one spoken line rather than copying a vanilla call scene.
Author and inspect in WolvenKit
- Build the journal tree and localization resource first. Reopen both and verify every full path and secondary key.
- Add the questphase journal nodes. Set concrete entry classes,
fileEntryIndex, notification flags, and incoming state sockets. - Wire the sequence by socket identity. Screen position is not execution order.
- Add one persistent sent guard and separate branch/completion facts. Avoid a single fact whose meaning changes halfway through the flow.
- For multiple choices, fan out to individual state waits, write the result on each branch, and converge only after the matching reply.
- For a required acknowledgement, wait on the final message’s visited state.
Do not treat a journal node’s
Outas acknowledgement. - Save, reopen, serialize a focused review copy, and resolve every node, socket, condition handle, and journal path.
- Pack and inspect framework logs before beginning runtime diagnosis.
Clean-save acceptance
Phone entries, visited flags, choice states, facts, scenes, and phone lifecycle can all be save-backed. Retain separate cases for:
- first install on an untouched save;
- save and reload before the opening message;
- save with the choice group active;
- one run for every choice from the same untouched baseline;
- save after the reply but before required final acknowledgement;
- completed-save reload and re-entry attempt;
- removal isolation from the original pre-install save;
- for calls, reject/interruption, save during the call, and phone-busy cases.
Record archive and loose-file hashes, save history, selected choice, journal state, facts, and fresh ArchiveXL/RED4ext/redscript logs. Resetting one fact does not clear a visited message or an active call.
Failure routing
| Symptom | Inspect first |
|---|---|
| Contact or message never appears | Journal registration, full path, entry class, fileEntryIndex, localization registration |
| Message appears twice | Sent guard timing, duplicate root entry, completed-save re-entry |
| Choice group appears but never advances | Wait path points to the group instead of the individual choice, or wrong state dimension |
| Both replies appear | Branch conditions, mutual-exclusion facts, and convergence wiring |
| Final message appears after completion | Completion used activation or a pacing delay instead of the intended visited gate |
| Call UI starts but no dialogue plays | Phone operation succeeded while scene input, actor/context, subtitle, VO, or WEM ownership failed |
| Dialogue ends but phone remains busy | Missing or unreachable EndCall, rejected/interrupted output not handled |
| Only one save behaves incorrectly | Saved journal, phone, scene, or fact state; repeat from the untouched baseline |
Branching, choices, and debriefs
A choice is made in one state domain and consumed in another. Phone entries, scene options, fact comparisons, switch cases, and graph joins are not aliases for one generic “branch” node.
Tested with Cyberpunk 2077 2.31a, WolvenKit 8.19.0, ArchiveXL 1.27.0,
RED4ext 1.30.0, and redscript 0.5.31. See Tested
versions.
The practical rule is simple: observe the player’s choice through the system that owns it, write one durable outcome, and make downstream phases branch on that outcome. Do not make a debrief rediscover a choice from presentation state.
Identify the branch domain
| Source of the decision | Observe it with | Persist it with | Do not substitute |
|---|---|---|---|
| Phone reply | Journal condition on the individual gameJournalPhoneChoiceEntry | questSetVar_NodeType on the winning route | Choice-group Active state |
| Scene option | Scene graph option/section routing, then a named questphase output or fact handoff | Fact writer immediately after the named result | Choice caption or embedded locstring ID |
| Current world/fact state | questConditionNodeDefinition for a snapshot, or a deliberate wait for later state | A result fact if later phases need the answer | A Pause Condition when an immediate false route is required |
| First satisfied asynchronous route | Parallel waits followed by a tested convergence node | Winner fact before convergence | The name “XOR” as proof that losing listeners are cancelled |
| N-way state selection | questSwitchNodeDefinition with case-bound conditions | Case-specific fact or phase | A chain of undocumented fall-through assumptions |
Read Immediate branches and waiting gates before choosing Condition versus Pause Condition, and Signal flow before adding AND, XOR, Hub, or Switch nodes.
State prerequisites
Define the outcome vocabulary before editing the graph:
- give each fact one stable meaning, such as
route_destroy_selected; - decide whether exactly one outcome, at least one outcome, or every outcome may be true;
- define the value domain—boolean
0/1, an integer enum, or separate facts; - reserve an explicit fallback for corrupt, missing, or legacy save state;
- decide where the choice becomes durable and where it becomes immutable;
- list every journal, scene, device, or world asset used only by one branch.
Separate boolean facts are easy to inspect but permit impossible combinations. An integer outcome fact avoids that combination but requires conditions for each supported value. Neither representation is safe without a clean-save and reload matrix.
Recipe: two mutually exclusive fact branches
This choice gate normalizes two phone-choice facts into two durable quest-outcome facts:
In1
+-> wait source_choice_a > 0 -> set outcome_a = 1 -> XOR In1 --+
`-> wait source_choice_b > 0 -> set outcome_b = 1 -> XOR In2 --+
|
XOR Out1
|
Out1
| Node | Decisive properties |
|---|---|
| Each wait | questPauseConditionNodeDefinition → questFactsDBCondition → questVarComparison_ConditionType |
| Comparison | comparisonType: Greater, branch-specific factName, value: 0 |
| Each write | questFactsDBManagerNodeDefinition → questSetVar_NodeType, branch-specific fact, setExactValue: 1, value: 1 |
| Convergence | questLogicalXorNodeDefinition, inputSocketCount: 2, outputSocketCount: 1, sockets In1, In2, Out1 |
Start both waits from the same input only when the source facts are guaranteed to be mutually exclusive. Have the upstream phone group write exactly one source fact. The gate then wrote a stable outcome fact for later activity and debrief phases.
Both outcome routes and XOR cancellation remain Experimental. Test each source fact separately, then deliberately set both facts. The graph should reject, guard, or route that conflicting state intentionally.
Recipe: evaluate one optional outcome at a boundary
An optional condition often means “record success or failure when the main activity reaches this boundary,” not “monitor continuously from activity start.” Use this shape:
In1
-> objective Active
+-> wait condition_fact > 0 -> set optional_success = 1 -> XOR In1 --+
`-> wait condition_fact == 0 -> set optional_failure = 1 -> XOR In2 --+
|
XOR Out1
|
objective Succeeded
|
Out1
| Property | Exact retained value or boundary |
|---|---|
| Positive route | questVarComparison_ConditionType, Greater, value 0 |
| Zero route | questVarComparison_ConditionType, Equal, value 0 |
| Result writes | Separate exact-value fact setters |
| Convergence | Two-input, one-output logical XOR |
| Presentation | The fixture activates and later succeeds a journal objective; its serialized optional field is 0 |
The last point matters: the fixture’s name does not prove an optional journal presentation flag. If the objective should render as optional, author and test that journal property separately.
Structurally validated: the reduced mod-owned phase passed handle
validation and a WolvenKit 8.17.4 deserialize/serialize round trip. It has no
retained in-game result and is not validation under WolvenKit 8.19.0.
This shape also has a bounded value domain: a negative fact satisfies neither
wait and stalls. If negative values are possible, use an immediate
questConditionNodeDefinition with explicit True and False routes, or add
a deliberately tested third route. The immediate node form is Observed in
vanilla in
base\open_world\street_stories\westbrook\japantown\sts_wbr_jpn_03\phase\sts_wbr_jpn_03_streetstory.questphase;
the same retained phase also shows the contrasting wait form.
Do not use this boundary recipe for a live stealth bonus. A continuous monitor must start beside the main activity and must have a stop/cut owner. See Monitors and cancellation.
Recipe: select one of N cases
questSwitchNodeDefinition binds condition items to numbered case sockets:
In
-> Switch First_Fulfilled
Case101 -> persist outcome 101 -> case activity
Case202 -> persist outcome 202 -> case activity
Case303 -> persist outcome 303 -> case activity
Otherwise -> explicit fallback
| Serialized part | Required relationship |
|---|---|
behaviour | The retained vanilla switch uses First_Fulfilled |
conditions[] | Each questConditionItem owns one condition handle and numeric socketId |
Case<socketId> | Output socket paired with that condition item |
Otherwise | Explicit additional output; connect it if missing state must not stall silently |
In | The execution signal that asks the switch to evaluate |
Observed in vanilla: the switch with local ID 5, nested beneath phase node
252, in
base\open_world\minor_activities\watson\northside\ma_wat_nid_15\ma_wat_nid_15_phase.questphase
uses First_Fulfilled, three fact conditions, their case sockets, and an
unconnected Otherwise socket.
That file proves the shape, not evaluation order, short-circuit rules,
All_Fulfilled, save restoration, or what happens when several conditions are
true. Those behaviors are Experimental. For authoring:
- make conditions exclusive whenever the narrative promises one outcome;
- give each case a durable result write before branch-specific side effects;
- connect
Otherwiseto a logged/recoverable fallback or explicit termination; - do not mutate conditions inside one case and expect undocumented fall-through;
- test every case, no case, and multiple-true cases from named saves.
Converge only after branch-owned effects
Convergence is where branch identity is most often lost too early. Keep this order:
choice owner
-> observe winner
-> persist winner
-> perform branch-only journal/world/inventory effects
-> converge
-> perform exactly-once shared continuation
If both branches grant the same reward, converge before one common reward node. If rewards differ, grant inside each branch, write a payout/result fact, then converge after both branches’ required effects. Never fan both routes directly into two copies of the same common payout.
A graph-level AND is appropriate only when execution must wait for several independent routes. When the requirement is “several predicates are true,” a single Pause Condition with a Boolean AND tree is easier to reason about and does not depend on partial signal-memory behavior. Vanilla AND, XOR, and Hub topologies are Observed in vanilla; general arrival memory, cancellation, and reload behavior remain Experimental unless the exact route has its own runtime evidence.
Carry a scene choice into the quest
Scene options belong to the scene’s screenplay and embedded localization store. Do not branch a questphase on the option’s caption or locstring ID. Instead:
- route the option inside the
.scene; - end each meaningful route through a named scene output, or write a scene-owned fact at a deliberate handoff;
- expose matching sockets on the
questSceneNodeDefinition; - write the durable quest outcome immediately after the named output;
- converge only after branch-specific scene/quest effects are complete.
Lab 5 demonstrates a Structurally validated single named exit. Test every exit when expanding it into a choice scene.
Authoring procedure
- Draw the player-facing alternatives and mark the system that owns each choice.
- Allocate source-state and durable-outcome facts separately.
- Add the smallest native condition or switch shape that represents the required timing.
- Set every comparison, path,
socketId, and socket count before connecting edges. - Write each outcome before any later phase can consume it.
- Complete branch-owned journal, inventory, scene, device, or world effects.
- Connect the convergence and exactly-once shared continuation.
- Save, reopen, and inspect resolved source/destination sockets from both endpoints.
- Round-trip a focused mod-owned resource with WolvenKit
8.19.0; label that result Structurally validated, not Runtime-proven.
Save and runtime matrix
Facts, phone choice state, scene state, journal entries, and active graph waits can survive in a save. Test at least:
| Case | Required observation |
|---|---|
| Outcome A | Only A state/effects occur; shared continuation occurs once |
| Outcome B | Only B state/effects occur; shared continuation occurs once |
| Every additional switch case | Correct case socket, durable fact, and branch assets |
| No case true | Otherwise or documented fallback occurs |
| Multiple cases true | Deliberately specified result; no accidental duplicate continuation |
| Save before choice | Reload still presents a valid choice once |
| Save after source choice, before durable write | No lost or duplicated outcome |
| Save after durable write, before convergence | Correct branch resumes and shared effects occur once |
| Completed-save re-entry | No branch side effect or reward repeats |
| Replay from untouched save | First-run behavior remains reproducible |
Use a separate copy of the same untouched baseline for each route. A console fact edit is useful for fault injection, but it is not clean-save evidence.
Failure routing
| Symptom | Inspect first |
|---|---|
| Wrong phone branch | Individual choice path, Succeeded state, and source fact write |
| Gate never leaves one branch | Fact name, comparison and value, whether the wait was started |
| Both branch effects occur | Mutual-exclusion invariant, stale save facts, losing listener, duplicate edges |
| Switch selects an unexpected case | Condition order, socketId to Case<id> mapping, multiple-true inputs |
| Switch stalls | Missing true condition and unconnected Otherwise |
| Optional result is never recorded | Negative/out-of-domain value, boundary never entered, or wrong result fact |
| Shared continuation runs twice | Convergence semantics, late listener, multiple incoming signals, absent one-shot guard |
| Correct choice but wrong debrief | Outcome was inferred again downstream instead of consuming the durable fact |
Areas, devices, and hacking
This cookbook page assembles three systems that are often mistaken for one: a world area or device must exist and stream, a questphase must address it through the correct identity, and the player-facing objective and marker must be managed separately. The graph never creates the trigger volume, interaction prompt, controller, hack minigame, or drop-point kiosk that it observes.
world ownership quest ownership
streaming block questQuestPhaseResource
-> Quest sector -> phasePrefabs root
-> trigger or device -> objective / mappin state
-> nodeData placement -> action or waiting condition
-> full child NodeRef -> local child NodeRef
Use this page after Triggers and areas, Quest prefabs and NodeRefs, and Devices and persistence. It is a cookbook, not a replacement for those ownership chapters.
Vanilla references
These focused base-game resources are useful comparisons. Extract them from your own installation; they are references, not files to copy into the project.
base\quest\main_quests\part1\q108\phases\q108_06b_tower_mainframe.questphase
base\open_world\street_stories\watson\little_china\sts_wat_lch_01\phase\sts_wat_lch_01_phase.questphase
base\open_world\street_stories\watson\little_china\sts_wat_lch_01\phase\sts_wat_lch_01_openworld.questphase
base\open_world\street_stories\watson\little_china\sts_wat_lch_01\phase\sts_wat_lch_01_combat.questphase
base\quest\side_quests\sq021\phases\sq021_randys_room.questphase
base\gameplay\devices\drop_points\drop_point.ent
Resource and asset checklist
Before opening the quest graph, inventory every owner. A blank cell is not a default; it is an unresolved design decision.
| Concern | Required owner | Review evidence |
|---|---|---|
| World namespace | Quest-prefab root plus a Quest streaming descriptor | Local root in phasePrefabs; matching full root in the descriptor |
| Area | worldTriggerAreaNode, AreaShapeOutline.buffer, notifier, and nodeData | Full child NodeRef, transform, bounds, decoded points, height, and questTriggerNotifier_Quest |
| Objective presentation | Journal objective, description, localization, and optional journal mappin | Full typed paths, correct fileEntryIndex, and registered localization |
| World marker | Usually a dedicated worldStaticMarkerNode in an AlwaysLoaded sector | Full marker NodeRef and world-space navigation endpoint |
| Device | worldDeviceNode or evidence-matched entity placement | Entity template, appearance, node-local instanceData, controller, components, slots/workspots, and full NodeRef |
| Quest-side device access | Prefab dependency plus action and/or condition | Exact controller class, action CName, condition function CName, and local target NodeRef |
| Persisted lookup | Only the evidence-required .devices or .psrep contribution | Exact NodeRef hash, controller class, merge result, and reload behavior |
| Plant item | TweakDB item record and inventory ownership | Item ID, grant/pickup owner, quantity, consumption rule, and save behavior |
| Drop point | Existing live kiosk plus its controller contract | Exact native NodeRef, walkable approach, reservation item, deposit fact, and separate marker endpoint |
phasePrefabs declares a namespace dependency; it does not place the sector.
A sector nodeRefs row registers an identity; it does not supply a concrete
trigger or device. A visible entity does not prove that its persistent
controller bound correctly. These are Observed in vanilla and
Structurally validated ownership distinctions, not runtime shortcuts.
Reach and leave areas
Story view
activate reach objective and marker
-> wait for the intended area relation
-> succeed reach objective and hide marker
-> activate leave objective
-> wait for the outer leave relation
-> succeed leave objective
-> optional completion fact or cleanup
The reach and leave waits must be ordered. If an outside-state condition is active before the player enters, it can already be true.
Exact phase payload
The retained player-targeted trigger condition has this focused property contract:
| Owner | Property | Meaning in the inspected shape |
|---|---|---|
questPauseConditionNodeDefinition | condition | Handle to the condition wrapper |
questTriggerCondition | triggerAreaRef | Local NodeRef beneath an available quest-prefab root |
questTriggerCondition | activatorRef | Empty entity reference in the inspected player form |
questTriggerCondition | isPlayerActivator | 1 in the inspected player form |
questTriggerCondition | type | Entered, Exited, IsInside, or IsOutside |
Observed in vanilla: the cited Q108 phase uses a player-targeted
questTriggerCondition with an empty activatorRef,
isPlayerActivator: 1, local triggerAreaRef, and IsInside.
Use Entered/Exited for crossings and IsInside/IsOutside for current
containment. Lab 3 uses the latter as ordered state waits:
| Condition | Use when the story means | Reload boundary |
|---|---|---|
Entered | Cross from outside to inside after activation | Loading already inside may not create a new edge |
Exited | Cross from inside to outside after activation | Starting outside is not a leave event |
IsInside | Proceed whenever the player is currently inside | Already-inside activation and reload require a retained test |
IsOutside | Proceed whenever the player is currently outside | Must be activated only after the intended inside step |
The world-side area needs an authoritative outline buffer, a quest notifier, a vertical band that intersects the route, and a full NodeRef beneath the same root. Check the player’s Z coordinate when a rooftop trigger never fires.
Marker ownership
Activate and clear the journal mappin on explicit graph edges. Do not assume a trigger or device NodeRef is also a useful marker anchor. A marker intended to exist before a Quest sector streams normally needs its own AlwaysLoaded owner. The retained direct-device mappin failure is documented in NodeRefs, streaming, and placement.
Experimental: whether a new native device can serve directly as a journal mappin target must be tested for that exact cross-world identity. A separate marker is a controlled design, not a universal engine requirement.
Meeting areas are a lifecycle, not one radius
A contact meeting normally separates broad setup from the final engagement:
request community activation
-> wait for CharacterSpawned
-> V enters the broad setup area
-> optional checkpoint
-> start scene at a scene marker
-> the running scene owns any narrower engage/awareness gate
-> receive a named scene exit
-> delay cleanup until no scene or AI owner remains
| Boundary | Owner | What it may establish |
|---|---|---|
| Streaming/setup | Quest sector, community area, and broad trigger after readiness | The world and contact have time to become available before scene start |
| Readiness | Spawn Manager action plus CharacterSpawned condition | The named community entry exists before scene acquisition |
| Engagement | Narrow trigger or interaction owned by the running scene | The player deliberately reaches the presentation point after scene setup |
| Scene origin | scnWorldMarker and scene start node | Scene transform and actor acquisition context |
| Cleanup | Named scene exit, later delay/area, and community deactivation | A reviewable lifetime after presentation ends |
For actor readiness and scene startup, follow Activation, readiness, and acquisition and Scene entry, exit, and quest handoff.
The native device contract
A quest action and a quest condition are consumers of a device that already exists:
worldDeviceNode
-> entityTemplate (.ent)
-> interaction/controller/scanner/workspot components
-> node-local instanceData RedPackage, when required
-> stable full NodeRef
questphase
-> optional questInteractiveObjectManagerNodeDefinition
-> questPauseConditionNodeDefinition
-> questObjectCondition
-> questDevice_ConditionType
The structurally validated focused properties are:
| Node or payload | Decisive properties |
|---|---|
questInteractiveObjectManagerNodeDefinition | type: questDeviceManager_NodeType |
questDeviceManager_NodeTypeParams | deviceAction, deviceControllerClass, objectRef, actionProperties, slotName |
questPauseConditionNodeDefinition | condition: questObjectCondition |
questDevice_ConditionType | deviceConditionFunction, deviceControllerClass, functionParameters, objectRef |
Keep four meanings separate:
- Observed in vanilla: the entity template and bound instance package own available interaction, controller, scanner, slot, and workspot behavior.
- Structurally validated: a device-manager node can send one named action, and a pause condition can wait on one named controller function.
- Runtime-proven: only the exact action/function/device combinations named in the evidence table have retained in-game results.
- Experimental: substituting a controller class, action, condition function, device template, or NodeRef creates a new runtime claim.
Player-driven versus quest-driven operation
Use a command node only when the quest itself should send an action. If the player must select a prompt, the graph should normally expose that interaction through the device asset and wait on its resulting controller state or fact. Sending the same action before the wait can complete or retrigger the device without player input.
Structurally validated: the retained generated device shape can omit the action node and connect presentation directly to the device condition.
Experimental: that omission does not prove a particular device exposes a prompt. Prompt availability still belongs to its template, instance package, TweakDB interaction record, slots/workspots, controller state, and placement.
Four bounded device recipes
Interact with a device
Use this smallest topology when one controller function is the completion signal:
objective Active
-> optional description and marker Active
-> optional device-manager action
-> wait questDevice_ConditionType
-> objective Succeeded
-> marker Inactive
-> optional success fact
Structurally validated: this is the exact generated research shape. It
does not synthesize the device, controller, prompt, animation, or the state
transition consumed by deviceConditionFunction.
Hack an access point
An access-point recipe has two additional owners: the access-point entity must provide the personal-link/minigame behavior, and its placement must leave a usable entry and exit workspot. The quest may enable the device first, but the player-driven route should wait for the native result rather than fire the same operation twice.
Experimental: a generic access-point tutorial remains incomplete until a mod-owned device passes approach, connection, minigame, success, cancel, disconnect, reload, and replay cases on the pinned stack.
Plant or upload an item
The reduced plant topology is:
| Order | Native node | Required binding |
|---|---|---|
| 1 | questJournalNodeDefinition | Objective Active |
| 2 | questInteractiveObjectManagerNodeDefinition | Device, controller class, action |
| 3 | questPauseConditionNodeDefinition | Same device/controller plus completion function |
| 4 | questItemManagerNodeDefinition | Local-player item removal |
| 5 | questJournalNodeDefinition | Objective Succeeded |
| 6 | questFactsDBManagerNodeDefinition | Exact completion fact |
Observed in vanilla: comparable plant-item structures occur in
sts_std_arr_05_openworld.questphase and the cited Q108 mainframe phase.
Structurally validated: the reduced template uses
questAddRemoveItem_NodeTypeParams with nodeType: RemoveAll, a concrete
item TweakDBID, the local-player universal reference, silent removal, and no
notification. Those exact removal choices are fixture properties, not
universal plant semantics.
Deliver to a drop point
A drop point is a live device and a separate navigation target. The retained
delivery shape first proves the selected item is present, then sends a
ReserveItemToThisDropPoint event to the kiosk and waits for the engine-owned
deposit fact.
| Event-manager property | Retained value or binding |
|---|---|
managerName | DropPointManager |
componentName | controller |
PSClassName | DropPointControllerPS |
objectRef | Exact native drop-point entity reference |
event | ReserveItemToThisDropPoint with the item TweakDBID |
The reservation event does not create the kiosk, add the item, choose a safe
location, or place a useful quest marker. Inspect
base\gameplay\devices\drop_points\drop_point.ent; its UI_Interaction,
poi_mappin, roleMappin, and main_slot/navQuery offsets are distinct in
the retained vanilla template.
Keep the device as the deposit target and place a separate navigation marker at a walkable approach point when the device origin produces a poor GPS route.
Author in WolvenKit
The exact editor layout changes between WolvenKit builds, but the native resource order is stable enough to review deliberately:
- Start from a mod-owned project and copy the completed Lab 3 or Lab 5 checkpoint into a new experiment; never edit an installed lab in place.
- Add the Quest descriptor and mod-owned sector resources. Declare one quest-prefab root and register every full child NodeRef under it.
- For an area, create the trigger node, placement, notifier, and authoritative outline. Reopen and decode the outline after saving.
- For a custom device, select an evidence-matched
.ent, then author the smallest node-local package or registry contribution required by that comparison. Do not copy a whole unrelated RedPackage. - In the questphase, add the objective and marker state nodes before adding the trigger or device condition. Use an action node only if the quest owns that action.
- Bind every local NodeRef to the root that the phase can actually resolve. For an external child, follow the parent/child ownership arrangement proved by its selected reference rather than duplicating roots by habit.
- Add explicit success, marker cleanup, inventory mutation, fact, and
community cleanup edges. Do not treat an activation node’s
Outas proof that the player completed the activity. - Save, reopen, serialize, and inspect the concrete node classes, non-null
handles, socket connections, NodeRefs, item IDs, and controller
CNamevalues. - Pack only mod-owned resources and ArchiveXL registrations. Extract the archive and compare its payload inventory with the intended project.
The Lab 3 and Lab 5 downloads remain the executable references for world and meeting ownership. This page does not claim that either download contains a custom device, plant interaction, or drop-point recipe.
Clean-save acceptance matrix
Use one untouched save made before the candidate existed, then make separate named child saves. Do not overwrite the only clean control.
| Case | What it distinguishes |
|---|---|
| Start outside the area | Premature state-condition or objective activation |
| Cross the boundary normally | Trigger geometry, notifier, activator, and graph order |
| Load before crossing | Restoration before an edge or state change |
| Load already inside while the wait is active | Entered versus IsInside behavior for this exact graph |
| Leave through two directions and elevations | Outline, height, bounds, and real site geometry |
| Approach the device before and after its sector streams | Visibility versus controller and interaction readiness |
| Cancel or disconnect mid-hack | Controller recovery and objective non-completion |
| Complete once, then reload | Duplicate action, reward, item mutation, and marker prevention |
| Stream away and return | Device, marker, and community reacquisition |
| Reinstall against an old save | Save-backed fact, journal, trigger, and persistent-device contamination |
| Remove the mod and load a disposable copy | Cleanup/isolation only; never risk the sole clean control |
For each test, note the starting save, player position and Z, selected interaction, result fact, inventory change, journal state, and relevant logs.
Troubleshooting
| Symptom | Inspect first |
|---|---|
| Objective appears but area never completes | Trigger notifier, local/full NodeRef chain, activator fields, condition type, outline buffer, and player Z |
| Leave completes immediately | Graph order and an already-true IsOutside condition |
| Meeting starts before the contact exists | Community activation, CharacterSpawned, broad setup order, and scene acquisition |
| Marker routes to the wrong place | Dedicated marker transform and navigation endpoint; do not assume the trigger/device root is walkable |
| Device renders but has no expected prompt | Entity template, interaction component, controller binding, workspot/slot, instance-package CRUIDs, and initial state |
| Quest cannot find a visible device | Prefab dependency, local/full NodeRef context, controller class, and evidence-required .devices registration |
| Hack completes before player input | A quest action was sent before a player-driven condition wait |
| Plant succeeds but item remains | Item TweakDBID, local-player reference, remove mode, quantity, and branch actually reached |
| Drop point has no deposit choice | Reservation event, exact item ownership, native kiosk reference, controller class, and deposit fact |
| Old files, prompts, or device state return | Save-backed device identity; use a fresh NodeRef or a pre-stream clean save |
| A packed candidate loads but behaves incorrectly | Treat serialization and runtime validity separately; inspect the producer of the waited state |
Continue with Items, shards, files, and scans for inventory and readable-content ownership, and Workspots and interactions for placement and interaction assets.
Items, shards, files, and scans
An item objective crosses several identifier domains. The inventory contains a TweakDB item record; a readable entry lives in the journal; a pickup or scan target has a world identity; and the questphase observes or changes those systems. Reusing one string across fields does not make those owners equivalent.
TweakDB item ID ---------> inventory count
^
world loot or quest grant ----|
journal path -----------> shard / file / email presentation
^
item Read action or computer --|
world object NodeRef ----> scan event ----> quest condition
This page teaches bounded native patterns for acquisition, readable shards, computer files, terminal documents, and ordered clue scans. It does not create a universal loot container, computer controller, scanner component, or custom item record.
Vanilla references
These focused base-game resources are useful comparisons. Extract them from your own installation; they are references, not files to copy into the project.
base\open_world\minor_activities\watson\northside\ma_wat_nid_15\ma_wat_nid_15_phase.questphase
base\open_world\minor_activities\watson\little_china\ma_wat_lch_03\ma_wat_lch_03_phase.questphase
base\open_world\minor_activities\watson\little_china\ma_wat_lch_05\ma_wat_lch_05_phase.questphase
base\open_world\minor_activities\watson\little_china\ma_wat_lch_15\phases\ma_wat_lch_15.questphase
base\open_world\street_stories\heywood\vista_del_rey\sts_hey_rey_09\phases\sts_hey_rey_09_openworld.questphase
base\quest\side_quests\sq021\phases\sq021_randys_room.questphase
base\worlds\03_night_city\_compiled\default\exterior_19_-8_0_0.streamingsector
base\gameplay\devices\masters\computers\laptop_1.ent
base\journal\cooked_journal.journal
Resource and asset checklist
| Concern | Owner | Required decision |
|---|---|---|
| Inventory identity | TweakDB item record | Exact TweakDBID, quantity, stack behavior, tags, and whether a readable action exists |
| Acquisition producer | World loot, device, scene, or quest item-manager node | Who actually adds the item and whether a notification is expected |
| Acquisition observer | questInventory_ConditionType or an explicit fact | Quantity comparison, player/object selection, and already-owned behavior |
| Readable content | Mod-owned journal tree | Leaf type, full path, localization, registration, and containing-group fileEntryIndex |
| Shard bridge | Item’s secondary Read action | Exact journal entry target and presentation behavior |
| Computer content | Entity template plus bound instance RedPackage | Controller chunks, component CRUIDs, non-empty file/email structures, menus, and per-element fact |
| Scan target | Streamed entity/device | Full/local NodeRef, scanner component, highlight owner, placement, and streaming range |
| Scan observer | questScan_ConditionType | Event type, object reference, activation order, and whether every clue is required |
| Objective presentation | Journal objective, description, optional clue entries, and mappins | Explicit Active/Succeeded/Inactive edges and registered localization |
| Persistence | Save plus world/device/journal state | Clean control, reload points, fresh device identity where needed, and replay policy |
The item ID, journal path, and world NodeRef are different identifier domains. Review Identifier domains before binding them.
Acquire an item
Choose the producer first
There are two useful small stories:
world pickup or other system adds item
-> quest waits until inventory quantity is sufficient
quest grants item
-> quest waits until inventory quantity is sufficient
The first pattern does not need an add node. The second does. In either case,
the wait is a separate assertion; the item-manager node’s Out means its
command was dispatched, not that every downstream inventory/UI effect has been
accepted.
Focused native properties
Structurally validated: the retained generated grant uses a
questItemManagerNodeDefinition with a handled
questAddRemoveItem_NodeType. Its parameter object contains:
questAddRemoveItem_NodeTypeParams property | Retained grant value |
|---|---|
entityRef | Local-player questUniversalRef |
itemID | Bound item TweakDBID |
nodeType | AddItem |
quantity | Bound positive quantity |
sendNotification | 1 |
flagItemAddedCallbackAsSilent | 0 |
The corresponding wait is:
questPauseConditionNodeDefinition
condition -> questObjectCondition
type -> questInventory_ConditionType
comparisonType: GreaterOrEqual
isPlayer: 1
itemID: Items.YourRecord
quantity: N
Observed in vanilla: inventory conditions occur in the retained vanilla corpus, including the cited Heywood street-story phase. The table above is a mod-owned reduced shape; its notification and player-reference choices are not universal vanilla rules.
Already-owned and repeatable cases
An inventory state wait can be true at activation if the player already owns the item. That is appropriate for “have the package” but wrong for “pick up this newly spawned package now.” Use a separate acquisition fact or resettable counter only when its producer is explicit.
Experimental: merely changing GreaterOrEqual to another enum or using an
object reference instead of isPlayer creates a new behavior claim. Inspect a
matching vanilla payload and test it rather than inferring semantics from the
name.
Remove, consume, or hand off an item
The retained plant template uses the same item-manager family with
nodeType: RemoveAll, silent callback, and no notification. The generated
outcome-device shape can also remove branch-specific items before adding an
outcome item or fact.
| Design question | Why it must be explicit |
|---|---|
| Remove one or every matching item | RemoveAll is not a quantity-one promise |
| Show a notification | Silent callback and sendNotification are separate fields |
| Consume before or after success | Graph order determines whether a failed interaction loses the item |
| Retry after reload | Inventory and completion facts may restore from different save-backed owners |
| Deliver to a kiosk | The drop point owns deposit UI and removal; do not also remove the item early unless the design proves that sequence |
Choose the removal quantity deliberately; do not use RemoveAll for every
quest item. See Areas, devices, and hacking for
the personal-link install and drop-point sequences.
Readable shards
A readable shard combines at least two assets:
TweakDB readable item
-> secondary Read action
-> full gameJournalOnscreen path
mod-owned journal
-> gameJournalOnscreenGroup
-> gameJournalOnscreen leaf
-> title / description localization
Activating a gameJournalOnscreen makes journal content available. It does
not by itself award an inventory item or guarantee a pickup overlay. Likewise,
owning or consuming a readable item does not establish a universal “the player
read every word” signal.
Choose what completion means
| Intended contract | Suitable observed signal | Evidence boundary |
|---|---|---|
| Player acquired the readable | Inventory ownership while it remains stacked | Structurally validated reduced inventory wait; reload and consumption still need testing |
| The award step occurred | Dedicated acquisition fact set by the known producer | Runtime-proven only for the exact 82C221... route |
| Player opened a full Journal reader | A matching visited condition, if the exact UI path emits it | Experimental for a generic inventory shard |
| Player opened the pickup-notification preview | No generic visited signal is established | Experimental; do not equate it with the full Journal reader |
Observed in vanilla: the retained phases associated with the
ma_wat_lch_03, ma_wat_lch_05, and ma_wat_lch_15 objectives named
read_shard do not contain questJournalEntryVisited_ConditionType. Their
progression is tied to inventory, loot, or interaction state instead. This is
evidence against a universal visited recipe, not proof that no quest anywhere
uses visited state.
Runtime-proven: the readable item in archive 82C221... was consumed into
the Journal, so inventory ceased to be a dependable completion wait. Its graph
instead observed the final scan’s acquisition fact, allowed a three-second
presentation window, succeeded the objective, and continued. It deliberately
did not prove that the pickup preview had been read.
Focused shard graph
The structurally validated reduced shape is:
optional objective Active
-> optional description Active
-> optional gameJournalOnscreen Active
-> wait acquisition fact OR inventory ownership
-> optional presentation delay
-> objective Succeeded
-> optional completion fact
Do not activate the same journal entry twice merely to force a notification. Keep acquisition, availability, notification, and proof-of-reading as separate claims.
Files, emails, and terminal documents
Journal leaves are presentation data
The onscreen journal branch contains distinct leaf families:
| Group | Leaf | Principal player-facing fields |
|---|---|---|
gameJournalOnscreenGroup | gameJournalOnscreen | title.value, description.value, tag, iconID |
gameJournalFileGroup | gameJournalFile | title.value, content.value, pictureTweak, videoResource |
gameJournalEmailGroup | gameJournalEmail | sender.value, addressee.value, title.value, content.value, optional media |
Observed in vanilla: these types and fields occur in
base\journal\cooked_journal.journal. A journal leaf does not create a
terminal menu or decide which device displays it.
For a path such as:
onscreens/emails/quests/minor_quest/your_quest/files/diagnostic
the containing files group is the path component used by
gameJournalPath.fileEntryIndex. Count the real path components from zero;
do not use the document’s array position or copy an index from another path.
The computer owns the clickable content
Observed in vanilla: SQ021’s laptop separates four owners:
world sector node
-> laptop_1.ent
-> node-local instanceData RedPackage
-> ComputerControllerPS
-> computerSetup.filesStructure[].content[]
-> journalPath
-> gamedeviceDataElement.questInfo.factName
questphase
-> wait for that fact
The inspected SQ021 resource joins its active controller package to the
controller component in laptop_1.ent through matching component CRUIDs. That
join is Observed in vanilla. The runtime behavior of a mismatched custom
package remains Experimental. Independently, filesMenu: 1 cannot name
clickable content when filesStructure itself is empty.
Do not copy SQ021’s full RedPackage. Inspect its three relevant chunks and construct a mod-owned minimal package whose controller/scanner component IDs match the exact selected template. Remove inherited mail, web, newsfeed, scanner, and quest content unless the new design owns it.
Terminal read graph
The structurally validated reduced graph is:
| Order | Node | Binding |
|---|---|---|
| 1 | Optional questJournalNodeDefinition | gameJournalFile path Active |
| 2 | questJournalNodeDefinition | Objective Active |
| 3 | questPauseConditionNodeDefinition | Dedicated document fact greater than zero |
| 4 | questJournalNodeDefinition | Objective Succeeded |
The phase does not infer a read from generic journal state. The computer element, computer scene output, or another explicitly authored UI owner must set the completion fact.
For a document-read gate, open the named file and confirm its fact changes. Then check that the quest advances; file visibility alone does not test the completion route.
Experimental: a generic email-click completion signal is not established by the file result. Files, emails, internet pages, and onscreens can use different device/UI owners.
Investigate clue scans
The scan target and the scan wait have different owners
The target must already be a streamed object with whatever scanner component and presentation behavior its template requires. The quest condition only observes it:
questPauseConditionNodeDefinition
condition -> questObjectCondition
type -> questScan_ConditionType
eventType: Finished
objectRef: gameEntityReference to clue NodeRef
Structurally validated: the current variable-length reduced shape uses
eventType: Finished, not Started. For each clue it can activate a mappin,
wait for the finished scan, hide that mappin, activate an optional
gameJournalOnscreen, set a clue fact, and grant one or more items.
The retained generated topology is ordered:
objective and description Active
-> clue 1 marker -> clue 1 Finished -> clue 1 outputs
-> clue 2 marker -> clue 2 Finished -> clue 2 outputs
-> ...
-> objective Succeeded -> optional completion fact
Observed in vanilla: ma_wat_nid_15_phase.questphase provides a broader
investigation comparison, and the retained Little China phases provide scan
condition shapes. Extract and inspect the exact target reference and event
payload; do not reuse their quest-local NodeRefs or facts.
The only Runtime-proven claim here is the narrow 82C221... result: its
final clue acquisition fact advanced the readable-shard stage after the item
had already been consumed into the Journal. Ordered three-clue completion,
reveal/conceal cleanup, and scanner-outline policy are Experimental because
no archive in this book’s ledger binds those broader results.
Experimental: requiring any N of M clues, unordered scans, scan reset,
repeated scans, evidence-board presentation, or a custom highlight layer needs
a different graph and acceptance campaign.
Author in WolvenKit
- Create or clone the mod-owned journal branch first. Add the precise
gameJournalOnscreen,gameJournalFile, orgameJournalEmailleaf and its localization, then register both resources through ArchiveXL. - For an inventory item, create or select a mod-owned TweakDB item record. Bind a readable action to the intended journal path only if the item should open that content.
- In the questphase, add explicit objective activation before the inventory, fact, or scan pause condition. Add a separate Succeeded state after it.
- For a quest grant or removal, add a
questItemManagerNodeDefinitionand inspect every parameter rather than copying a whole node blindly. - For each scan, bind
questScan_ConditionType.objectRefto the exact local target NodeRef made available by the phase’s prefab ownership. Confirm the corresponding full world registration and concrete target. - For a terminal, inspect the exact
.entcomponent IDs and author the smallest node-local package with non-empty content. Bind the device element’sjournalPathandquestInfo.factNameto mod-owned values. - Add marker cleanup, clue journal activation, fact writes, item grants, and presentation delay only on explicit post-condition edges.
- Save and reopen every CR2W. Serialize it and review concrete nested types,
handle references, item IDs, journal class names,
fileEntryIndex, scaneventType, target refs, and graph sockets. - Pack, extract, and compare the archive payload inventory before runtime testing. Serialization is Structurally validated, not Runtime-proven.
No existing lab download contains all of these systems. Lab 1 supplies the journal/localization foundation; Lab 3 supplies world NodeRef ownership. Copy their completed projects into a new experiment rather than treating this page as a hidden template.
Clean-save acceptance matrix
| Case | What it distinguishes |
|---|---|
| Start with zero items | Negative control for an inventory wait |
| Start already owning the required quantity | Intended state semantics versus a new-acquisition requirement |
| Acquire from the real world source | Producer, notification, inventory update, and quest wait |
| Save while waiting, then acquire after reload | Condition restoration and duplicate-grant prevention |
| Open the pickup preview and the full Journal separately | Preview behavior versus visited state |
| Consume the readable item | Whether the chosen completion signal survives item removal |
| Open the terminal before its objective | Pre-set fact and save-backed device/journal behavior |
| Open the terminal during the objective | Exact content element, fact, and single completion |
| Scan clues in intended order | Marker handoff, target identity, Finished, and outputs |
| Attempt a later clue early | Ordered topology and inactive-target presentation |
| Stream away and return mid-investigation | Target, marker, condition, and highlight reacquisition |
| Reload after completion | Duplicate item, journal entry, fact, objective, and marker prevention |
| Reuse an old device save after changing content | Persistent controller contamination; repeat with a fresh NodeRef or pre-stream save |
Retain the exact archive and resource paths, TweakDB item records, full journal paths, scan target NodeRefs, inventory counts, facts, journal states, starting save history, and framework/game logs.
Troubleshooting
| Symptom | Inspect first |
|---|---|
| Item objective completes immediately | Already-owned quantity, comparison type, and whether acquisition rather than ownership was intended |
| Grant node runs but no expected item appears | Item record existence, exact TweakDBID, local-player reference, quantity, and TweakXL load |
| Plant or handoff leaves the item | Remove mode, item ID, branch route, and whether the device/drop point owns removal instead |
| Shard appears but the quest stalls after opening it | Inventory consumption, preview versus full-reader state, and the actual acquisition/read signal |
| Journal entry is blank | Leaf type, full path, containing-group fileEntryIndex, ArchiveXL journal registration, and localization |
| Computer has no Files tab | Empty filesStructure, wrong controller chunk, component CRUID mismatch, content assignment, or stale saved device state |
| File opens but fact stays zero | gamedeviceDataElement.questInfo.factName, bound controller, or scene/UI output owner |
| Scan never completes | Target NodeRef, streaming, scanner component, eventType, object-reference form, and whether the condition was active |
| Scan completes on the wrong object | Local/full identity collision, prefab root, or copied target reference |
| Highlight remains after scanner exit | Determine whether the template or quest owns it; remove the custom layer before adding more cleanup events |
| Later clue appears too soon | Graph order, marker activation edges, and accidental parallel wiring |
See Messages, files, emails, and onscreens for complete journal leaf fields, Condition payloads for pause-condition anatomy, and Areas, devices, and hacking for the world and device sides of these patterns.
Workspots and interactions
“Workspot” is used around several systems, but those systems do not share one
owner. A placed AI spot can reference a .workspot activity for a community
actor. A device entity can carry its own interaction components, workspots,
and slots. A scene can acquire an actor whose AI or workspot state was prepared
elsewhere. The quest graph coordinates these assets; it does not manufacture
them.
community actor activity
community entry/phase/time
-> spot NodeRef
-> worldAISpotNode
-> AIActionSpot.resource (.workspot)
player/device interaction
worldDeviceNode or worldEntityNode
-> entityTemplate (.ent)
-> interaction components / slots / workspots
-> controller state or emitted fact
-> quest wait
Vanilla references
These focused base-game resources are useful comparisons. Extract them from your own installation; they are references, not files to copy into the project.
base\open_world\minor_activities\westbrook\japantown\ma_wbr_jpn_13\community\ma_wbr_jpn_013_claws_com.community
base\open_world\street_stories\watson\little_china\sts_wat_lch_01\phase\sts_wat_lch_01_combat.questphase
base\gameplay\devices\drop_points\drop_point.ent
base\gameplay\devices\masters\computers\laptop_1.ent
base\quest\main_quests\part1\q108\phases\q108_06b_tower_mainframe.questphase
base\workspots\common\ground\generic__stand_ground_cigarette__smoke__01.workspot
base\workspots\common\ground\generic__stand_ground__guard__02.workspot
base\workspots\patrolling\guard_stand.workspot
Choose the interaction family
| Player-facing design | Primary asset owner | Quest-side responsibility | Current status |
|---|---|---|---|
| Stationary contact waits at a pose | Community plus one worldAISpotNode and .workspot | Activate, wait for spawn, acquire in scene, then clean up | Test pose, acquisition, and cleanup at the selected location |
| Guard or civilian follows a finite spot sequence | Community time period plus multiple AI spots and compatible AI behavior | Activate, observe milestones if needed, stop/clean up | Shapes are Structurally validated; general movement behavior is Experimental |
| Player uses, plugs into, or hacks a device | Device .ent, instance package, controller, interaction/workspot/slot | Enable only if required, wait on a controller function or fact | Test the selected controller, action, and interaction |
| Quest waits until an actor is in a workspot | Character condition targeting the actor | Activate the wait only after the producing AI/workspot route | Condition family is Observed in vanilla; test completion and reload |
| Scene needs an actor at a presentation point | Community/AI preparation plus scene marker and actor acquisition | Wait for readiness, start scene, route named exit | Test readiness, actor placement, and the named exit |
This choice prevents a common category error: adding a
questCharacterWorkspot_ConditionType cannot place an actor into a workspot,
and adding a device-manager node cannot create the device’s prompt.
AI workspot ownership
Registry-side route
The retained community shape is:
communityCommunityTemplateData
entries[]
communitySpawnEntry
characterRecordId
entryName
phases[]
communitySpawnPhase
phaseName
appearances[]
timePeriods[]
communityPhaseTimePeriod
hour
isSequence
quantity
spotNodeRefs[]
| Property | Owner and meaning in the inspected shape |
|---|---|
characterRecordId | Community entry; chooses the character TweakDB record, not the world placement |
entryName | Community-local identity used by activation, conditions, and scene acquisition |
phaseName | Community-local mode selected by initial state or Spawn Manager operations |
spotNodeRefs | Full or context-valid references to placed AI spots |
isSequence | Period-level sequence flag in the retained shape; not a complete movement recipe |
quantity | Requested actors for that period; not a readiness or scene-performer count |
entryActiveOnStart belongs to registry initial state. For a controlled quest
contact, starting inactive and using an explicit activation edge makes the
lifecycle reviewable. That is a design used by the current lab, not a
universal vanilla requirement.
Compiled-area mirror
The compiled community area repeats the identity route rather than the character record:
| Registry value | Compiled-area value |
|---|---|
communitySpawnEntry.entryName | communityCommunityEntrySpotsData.entryName |
communitySpawnPhase.phaseName | communityCommunityEntryPhaseSpotsData.entryPhaseName |
communityPhaseTimePeriod.hour | communityCommunityEntryPhaseTimePeriodData.periodName |
communityPhaseTimePeriod.isSequence | communityCommunityEntryPhaseTimePeriodData.isSequence |
communityPhaseTimePeriod.spotNodeRefs | communityCommunityEntryPhaseTimePeriodData.spotNodeIds |
The registry, compiled area, sector nodeRefs, node placement, and persistent
spot data must identify the same route. In the compact lab shape,
workspotsPersistentData[].globalNodeId also joins the registry to the placed
spot’s world-global identity.
Structurally validated: these exact joins are checked for Lab 5. A stale area mirror can still serialize while leaving the actor unable to acquire the intended spot; successful joins remain a runtime question.
The placed AI spot
The world node owns placement and the activity reference:
worldAISpotNode
isWorkspotInfinite
isWorkspotStatic
lookAtTarget
spot -> AIActionSpot
resource -> ResourcePath to .workspot
snapToGround
useClippingSpace
Its matching nodeData owns position, orientation, scale, bounds,
NodeIndex, and full QuestPrefabRefHash. The workspot resource does not
choose the actor or world transform.
| Concern | Correct owner |
|---|---|
| Actor selection and appearance | Community entry and character/entity/appearance chain |
| Activity or animation setup | Referenced .workspot resource |
| Position and facing | AI spot nodeData transform |
| Quest-prefab identity | Full/local NodeRef chain and placement hash |
| Continuing versus finite use | isWorkspotInfinite plus the community-period design |
| Ordered route | Multiple unique spots, mirrored order, isSequence, compatible AI, and navigable placement |
| Readiness | Spawn Manager request followed by the matching character/spawning condition |
Check the workspot animation as well as actor spawning. A visible, passive actor can still be using the wrong pose or workspot.
Device interaction assets
Device interaction does not normally use a community worldAISpotNode.
Instead, the placed entity points to a template whose component hierarchy can
own interaction components, personal-link workspots, controllers, scanner
components, and slots:
worldDeviceNode or worldEntityNode
entityTemplate -> device.ent
instanceData -> optional RedPackage overrides
nodeData -> world transform and full identity
device.ent
components
workspots / interaction definitions
attachment and navigation slots
Observed in vanilla: drop_point.ent has different local transforms for
UI_Interaction, poi_mappin, roleMappin, and main_slot/navQuery.
Selecting the kiosk root for an icon, interaction, and GPS endpoint therefore
produces different world-space results from selecting the appropriate slot.
Observed in vanilla: SQ021’s laptop binds node-local persistent controller
data to laptop_1.ent through matching component CRUIDs. This establishes the
join, not mismatch behavior or permission to copy SQ021’s package. The runtime
behavior of a custom package whose component IDs do not match remains
Experimental.
Placement is part of the interaction
Check the actor/player entry pose, exit pose, facing, clearance, collision, ground contact, and navigation approach at the final transformed placement. Rotating an entity can rotate an interaction workspot away from the available floor even when the visible prop looks acceptable.
Test the personal-link interaction at its actual device placement. Check approach, collision, prompt visibility, connection, and exit pose.
Doors are device contracts
A world door is not an abstract graph gate. The placed door entity, its controller/persistent-state class, NodeRef, interaction setup, animation and collision, and surrounding navigation must already agree before a quest node can operate or observe it.
Keep world doors separate from vehicle doors. A vehicle trunk or seat door can
use questToggleDoor_NodeType with vehicle-specific identity and door-slot
fields; that is not the same contract as sending an action to a placed world
device.
Observed in vanilla: the cited Q108 phase contains
questDeviceManager_NodeTypeParams bound to DoorControllerPS and exact door
NodeRefs. Focused operations include ForceDisabled for
#q108_dvc_door_to_soulkiller and ForceCloseImmediate for
#q108_dvc_door_to_mainframe_interior. Those names and actions belong to those
doors in that quest. They establish the native manager/controller shape, not a
portable list of actions for every door template.
A bounded quest-driven world-door sequence is:
prove the placed door and controller resolve
-> optional objective/presentation Active
-> questInteractiveObjectManagerNodeDefinition
questDeviceManager_NodeType
exact objectRef + DoorControllerPS + evidence-matched deviceAction
-> observe an authoritative controller condition or downstream fact
-> advance presentation
-> restore or preserve the intended final door state on every exit
Before authoring, answer:
| Boundary | Required decision |
|---|---|
| Door asset | Which .ent, instance data, components, controller class, and persistent identity own the door? |
| Quest command | Is the quest supposed to force a state, or must the player use an asset-owned prompt? |
| Completion | Which exact controller function, fact, trigger, or later event proves the promised result? |
| Traversal | What happens to collision, navigation, companions, combatants, and the player if the action fails or the door closes? |
| Lifecycle | Should interruption, failure, reload, stream return, and completion restore, preserve, or re-evaluate the state? |
Experimental: this book has no retained generic mod-owned door candidate.
Do not infer that ForceOpen, ForceCloseImmediate, QuestLockAll, or another
string works on a newly chosen controller just because the name occurs in a
vanilla phase. Extract an evidence-matched door route, inspect its entity and
controller context, then test wrong-side approach, repeated use, obstruction,
companion traversal, combat, save/load on both sides, interruption, stream
return, completion, and removal isolation with a fresh persistent identity.
Quest-side coordination
For a passive contact, keep the order explicit:
community Activate
-> wait CharacterSpawned for the named entry
-> wait approach or interaction boundary
-> scene acquires the same community/entry
-> named exit advances the quest
-> delay cleanup past every active owner
-> community Deactivate
For a player-driven device interaction:
objective Active
-> optional quest command that merely enables/configures the device
-> player uses the asset-owned interaction
-> wait controller function or explicit fact
-> objective Succeeded
-> cleanup / item mutation / completion fact
Do not route a quest-issued action that performs the interaction if player input is the acceptance requirement. See Areas, devices, and hacking for the exact device-manager and condition payloads.
Workspot conditions are observers
Observed in vanilla: the retained condition corpus contains four
questCharacterWorkspot_ConditionType payloads beneath
questCharacterCondition, including a focused example in
sts_wat_lch_01_combat.questphase.
Experimental: this page does not prescribe the payload’s runtime enums, actor-reference form, transition behavior, already-true behavior, or reload semantics. Extract the exact comparison, record every decisive field, identify the AI or scene that produces the workspot state, and reduce it into a mod-owned fixture before using it as a completion gate.
Author a community workspot in WolvenKit
- Start from a copy of the Lab 5 project. Give the new quest-prefab root, community, entry, phase, period, and spot unique mod-owned identities.
- Select one
.workspotpath from an evidence-matched vanilla comparison. Inspect it, but reference the depot asset rather than extracting and redistributing it. - Add a
worldAISpotNodeto the Quest sector. Setspotto anAIActionSpot, bind itsresource, and choose finite/infinite and static fields deliberately. - Add the matching
nodeDataplacement and full child NodeRef. Confirm the transform leaves clearance and the facing matches the intended player approach. - Add the spot to the sector’s
nodeRefsand to the compact registry’s persistent spot data with the same world-global identity. - Bind the registry entry’s
spotNodeRefs, then mirror entry, phase, period, order, and spot IDs in the compiled community area. - Keep the entry inactive until the quest requests it unless a known-good
comparison requires another lifecycle. Add a
CharacterSpawnedreadiness wait before a scene or interaction tries to acquire the actor. - Save and reopen every CR2W. Serialize and inspect all handles, NodeRefs, global IDs, character records, appearance names, period names, and workspot resource references.
- Pack and extract the project, then compare payloads and ArchiveXL registrations before entering the game.
Author a device interaction in WolvenKit
- Inspect the exact device
.ent: controller, scanner, interaction components, component IDs, workspots, and relevant slots. - Place a mod-owned
worldDeviceNodeor evidence-matched entity node with a stable full NodeRef and a transform that leaves the interaction path clear. - Add the smallest node-local instance package needed for content or state. Preserve template-matched component identities; do not paste an unrelated RedPackage.
- Add a sparse
.devicescontribution only when controller lookup evidence requires it. Add.psreponly when a persistence test establishes that requirement. - In the questphase, expose or enable the device without prematurely performing the player action. Wait on the exact controller function or fact the interaction produces.
- Keep a separate marker at a verified HUD/navigation transform if the entity root or interaction slot is not a useful route endpoint.
- Reopen, serialize, pack, and inspect the complete template/package/NodeRef/ controller chain before runtime testing.
The Lab 5 download is the executable community/AI-spot reference. It does not contain a general player-interaction workspot or custom device recipe.
Clean-save acceptance matrix
| Case | What it distinguishes |
|---|---|
| Approach from outside streaming range | Sector, community area, spot, and device availability |
| Activate the community once | Entry/phase/period selection and intended spot acquisition |
| Load before activation | Clean negative control for active-on-start leakage |
| Load after activation but before interaction | Saved community/workspot or device state |
| Approach from several directions | Clearance, facing, navmesh, collision, and prompt range |
| Interrupt before completion | Actor/device recovery and objective non-completion |
| Complete normally | Producer signal, quest wait, presentation, and cleanup order |
| Enter combat during the activity | AI/workspot interruption policy; only include if the design supports combat |
| Stream away and return | Reacquisition without duplicate actor, stuck spot, or missing prompt |
| Reload after the named scene/device exit | Persistent owner release and duplicate prevention |
| Replay from untouched clean save | One-shot state and deterministic placement |
| Reuse an old device identity after changing its package | Demonstrates save contamination; repeat with a fresh identity |
When diagnosing a workspot, note its resource path, actor record, community entry, spot NodeRef, transform, device component IDs, starting save, and logs. Watch the animation through entry, completion, and interruption.
Troubleshooting
| Symptom | Inspect first |
|---|---|
| Actor spawns at the wrong spot | Registry spotNodeRefs, area spotNodeIds, full NodeRef, global ID, and sector placement |
| Actor clips, floats, or faces away | AI spot transform, workspot assumptions, ground contact, clearance, and navmesh |
| Actor activates but never settles | Workspot resource, finite/infinite fields, phase/period, AI role, and navigation |
| Sequence stalls or skips | Unique spot identities, mirrored order, isSequence, compatible AI, and interruption state |
| Scene cannot acquire the actor | Community and entry names, readiness wait, active phase, streaming, and scene actor reference |
| Actor disappears during the scene | Cleanup edge, scene named exit, AI/workspot ownership, and deactivation timing |
| Device is visible but prompt is absent | Exact .ent, interaction component, slot/workspot, controller binding, instance data, and initial state |
| Prompt appears from the wrong side | Entity transform, slot/workspot local transform, collision, and approach space |
| Quest waits forever after interaction | Controller function/fact producer, NodeRef context, and whether a quest command replaced player input |
| Removed interaction content returns | Save-backed persistent device identity; use a new NodeRef or a pre-stream save |
| Workspot condition never changes | Producing AI/scene lifecycle, actor reference, exact payload fields, activation order, and streaming |
For community structure, continue with Entries, phases, and AI spots and Cleanup and character safety. For device ownership, use Devices and persistence.
NPC interaction and meetings
A reliable meeting is a handoff between four owners: the quest requests an actor, the community materializes it, the world supplies a safe approach and performance location, and the scene acquires the actor for a bounded performance. A visible NPC is not yet a scene performer, and a completed spawn command is not proof that the NPC is ready.
This pattern produces the following native route:
activate objective and contact pin
-> activate community entry or phase
-> wait for the required actor to spawn
-> wait for V to enter a broad setup area
-> launch the scene at its world marker
-> consume one named scene exit
-> advance journal and branch state
-> wait until V leaves the cleanup area
-> deactivate or transfer the community lifecycle
Use Lab 5: First Contact as the downloadable, Structurally validated example. This page generalizes its ownership decisions; it does not turn the example into a universal template.
Prerequisites
Before opening the questphase in WolvenKit, prepare these explicit resources:
| Owner | Required material |
|---|---|
| Quest | Registered root, child phase, objective, description if used, map pin, one-shot state, and every intended scene outcome |
| World | Root-owned quest prefab, community registry node, compiled community area, AI spot, setup and cleanup triggers, scene marker, and map marker |
| Community | One entry/phase join, character record, appearance, quantity, activation policy, and workspot mapping |
| Scene | scnSceneResource, community-acquired actor, public entry, named exits, screenplay, events, and localization/audio dependencies |
| Lifecycle | Interruption policy, combat policy, delayed cleanup, completed-save behavior, and removal isolation |
Read Registries and areas, Entries, phases, and AI spots, and Scene resource anatomy before composing the route. If any local NodeRef is still ambiguous, resolve the prefab scope first with Quest prefabs and NodeRefs.
Vanilla references
These three resources show the activation, readiness, and scene-acquisition handoffs used by a small vanilla meeting. Extract them into a separate research project:
base\quest\minor_quests\mq003\mq003_orbitals.questphase
base\quest\minor_quests\mq003\phases\mq003_homeless.questphase
base\quest\minor_quests\mq003\scenes\mq003_01_homeless.scene
Four ownership handoffs
questphase requests activation
-> community resolves entry and phase
-> world streams area and AI spot
-> quest waits for named readiness
-> scene acquires community actor
-> scene publishes named outcome
-> quest persists progression
-> quest releases or transfers actor
The arrows are contracts, not implicit engine conversions:
- a Spawn Manager action does not wait for streaming or entity construction;
CharacterSpawneddoes not prove a scene can resolve a misspelled entry;- a scene actor definition does not activate its community;
- a scene End node does not create a questphase output with the same meaning;
- a named scene exit does not update journal state or clean up the actor;
- community deactivation does not prove immediate, safe disappearance.
Activate at the intended scope
The questphase action uses this focused native shape:
questSpawnManagerNodeDefinition
actions[]
questSpawnManagerNodeActionEntry
type -> questCommunityTemplate_NodeType
action: Activate
spawnerReference: <community NodeRef>
communityEntryName: <entry CName or None>
communityEntryPhaseName: <phase CName or None>
Choose one scope deliberately:
| Scope | Entry/phase values | Appropriate use |
|---|---|---|
| Whole community | None / None | The quest owns every configured entry and intends to bring the complete community online |
| Named phase | Exact entry and phase CName values | One actor or one authored community phase must change without activating unrelated entries |
None is an authored CName token in the observed serialized shape. It is
not null, an omitted property, or an empty string. The Spawn Manager’s Out
socket means only that the action was issued.
For a reusable contact, start inactive. An active-on-start entry can hide an ordering bug by making the actor happen to exist before the quest asks for it. It also makes clean-save and removal behavior harder to distinguish.
Wait for the actor you actually need
A one-entry whole-community readiness check can use:
questPauseConditionNodeDefinition
condition -> questCharacterCondition
type -> questCharacterSpawned_ConditionType
objectRef.reference: <community NodeRef>
comparisonParams
comparisonType: Greater
count: 0
entireCommunity: 1
That observed payload asks whether the community-scoped spawned count passes
the comparison. It does not mean every configured actor is ready. For a
meeting with several required performers, narrow the gameEntityReference
with exact entry names and wait for each performer before converging through
an explicit all-of join.
Do not substitute another identity domain for objectRef.reference:
| Wrong value | Why it is wrong |
|---|---|
| Registry node’s numeric world identity | Placement identity is not the community NodeRef used by the quest condition |
Compiled area’s sourceObjectId | It joins compiled area to template; it is not a text NodeRef |
| AI spot global ID | It identifies the placed spot, not the community source |
| Entry name alone | It narrows an entity reference only when paired with the correct community reference |
Character TweakDBID | It selects character data; it does not identify this spawned community instance |
See Activation, readiness, and acquisition for the complete identity join.
Give streaming an approach boundary
Actor readiness and world readiness are related but different. Place a broad setup trigger around the meeting site and wait for the player with:
questPauseConditionNodeDefinition
condition -> questTriggerCondition
triggerAreaRef: <setup trigger NodeRef>
type: IsInside
isPlayerActivator: 1
activatorRef: <empty/default entity reference>
Use a worldTriggerAreaNode whose notifier includes the quest consumer. The
outline buffer, sector placement, bounds, and prefab path all need independent
review; see Triggers and areas.
The setup radius is an authoring decision, not a universal number. It should be large enough to cover fast travel, vehicle approach, and streaming latency, but not so large that the scene begins before its intended presentation space is loaded. Test slow walking, sprinting, a fast vehicle, teleport-based debug arrival, and stream-away/return.
Let the scene acquire; do not ask it to spawn
The scene’s community-backed performer uses:
scnActorDef
acquisitionPlan: community
actorId: <scene-local scnActorId>
actorName: <debug/authoring name>
communityParams -> scnCommunityParams
reference: <same community NodeRef>
entryName: <same entry CName>
The decisive joins are exact:
| Field | Must agree with |
|---|---|
communityParams.reference | Spawn action, readiness condition, prefab scope, and mounted community source |
communityParams.entryName | Registry template entry and compiled-area entry mirror |
actorId | Screenplay sections, events, performer mappings, and actor-facing scene nodes |
actorName | Scene debug symbols; it is not the community entry join key |
If the actor is visible but the scene cannot acquire it, compare these joins before moving the actor or adding delays. More delay cannot repair an identity mismatch.
Publish a named quest/scene contract
The questphase launches the archived scene with
questSceneNodeDefinition. Its key properties are:
| Property | Contract |
|---|---|
sceneFile | Soft depot path to the packed .scene resource |
sceneLocation | scnWorldMarker whose nodeRef resolves beneath the active prefab scope |
| Input socket | Exact public scene entry, such as start |
| Output sockets | Exact public scene End names, such as contact_done, accepted, or refused |
interruptionOperations | Explicit interruption policy; an empty array is a bounded design choice, not a default guarantee |
CutDestination | Cut target only after its behavior has its own runtime evidence |
Inside the scene, publish the same names with the Start and End nodes. Do not
invent a generic questphase output called end. A scene End node named
contact_done must be consumed through a questphase output named
contact_done.
For choices, expose one named output per durable story result:
scene: accepted -> questphase: accepted -> set accepted fact -> advance journal
scene: refused -> questphase: refused -> set refused fact -> alternate route
scene: Default INT / Default RET -> explicit interruption policy
Persist the branch result after the scene releases its performer and before cleanup can erase the world actor. Keep the scene’s screenplay choice state, quest facts, and journal outcome as separate systems; see Entry, exit, and quest handoff.
Manual WolvenKit authoring order
Work from a copy of a known-good project, with the game closed:
- Mount the registry, compiled area, AI spot, triggers, and markers under one verified quest-prefab scope.
- Confirm the registry entry and compiled-area mirror use the same entry and phase names, and that their world identity joins are correct.
- Set the contact inactive on start unless the surrounding quest deliberately owns an earlier activation.
- In the child questphase, add objective and mappin activation before the Spawn Manager action.
- Add the named or whole-community
Activatepayload. - Add actor-specific readiness waits. Use an explicit all-of join when more than one performer is mandatory.
- Add the broad player
IsInsidesetup wait. Add a checkpoint only when the save/retry policy has been designed and tested. - Add
questSceneNodeDefinition, its soft scene path, world marker, public entry, every named success/refusal output, and interruption outputs. - Route each named output through its own durable fact/journal writes before convergence.
- Inactivate the meeting pin, activate any leave objective, and wait for the player to be outside a generous cleanup area.
- Deactivate the community, or explicitly transfer ownership to the next escort, combat, or scene phase. Do not do both.
- Serialize every changed CR2W with WolvenKit
8.19.0, reopen it, verify handles and sockets, pack, extract the archive, and compare the intended depot paths before launching the game.
Transfer ownership instead of cleaning up
Some meetings lead directly into following, combat, or another scene. In that case, preserve the community and hand off through a durable fact or named questphase output:
meeting named exit
-> persist outcome
-> scene releases actor
-> next phase assigns gameplay AI or follower role
-> later owner performs cleanup
Do not deactivate and immediately reactivate the same actor between adjacent activities. That introduces a streaming/materialization race and can restore the actor at its original AI spot. The next owner must state when it clears a follower role, restores an AI phase, or deactivates the community.
Unique story characters add another boundary. Do not borrow a story-managed actor merely because its record has a suitable appearance or voice. Prefer a quest-owned generic record unless you have proved the borrowed character’s global lifecycle, save compatibility, and other-quest interactions. See Cleanup and character safety.
Save, interruption, and cleanup matrix
Use separate untouched save descendants for stateful cases. Do not repeatedly overwrite one slot and call the runs independent.
| Case | Required observation |
|---|---|
| Slow ordinary approach | One activation, one actor, one scene start, correct named return |
| Fast arrival | Setup wait does not outrun actor or scene-marker streaming |
| Stream away before setup | Return restores one actor and one armed meeting route |
| Save after activation, before readiness | Reload reaches readiness without duplication or deadlock |
| Save after readiness, before scene | Reload either resumes the documented pre-scene state or is explicitly unsupported |
| Every named scene exit | Exactly one durable branch result and correct journal/mappin state |
| Leave during approach | Meeting remains recoverable or follows an authored cancellation result |
| Combat or police intrusion | Actor behavior, scene interruption, and cleanup follow the stated policy |
| Post-scene cleanup | Actor remains until the scene is released and V crosses the cleanup boundary |
| Completed-save reload | Meeting, marker, scene, and community do not reactivate |
| Clean replay | The same installed build completes from a separate untouched save |
| Removal isolation | Removing the candidate does not leave a save-dependent claim disguised as a clean resource result |
Test active-line save/load, scene interruption, and cut behavior separately from the normal named-exit route.
Common failures
| Symptom | Inspect first |
|---|---|
| Actor never appears | Registry active-on-start policy, activation scope, entry/phase spelling, area/template identity join, AI spot, sector bounds |
| Spawn action exits but wait never resolves | objectRef.reference, entry narrowing, comparison scope, stream coverage |
| Actor is visible but scene stalls | Scene communityParams.reference and entryName, performer mapping, scene marker, public entry |
| Scene runs but quest does not continue | Scene End name versus questphase output socket and connected edge |
| Actor disappears during dialogue | Premature Deactivate, competing phase change, stream bounds, or another owner clearing the actor |
| Actor walks back after dialogue | Follower/gameplay role cleared before the next lifecycle owner takes over |
| Reload uses an older route | Save history, active questphase/checkpoint, scene state, installed archive filename, and duplicate archives |
| New archive crashes at scene launch | Scene IDs, actor/lipsync slot mapping, handle topology, scene path, and retained crash evidence before changing world geometry |
For symptom-led isolation, use Actors, scenes, and lipsync and Save state and clean retests.
Stealth, combat, and destruction
Stealth, combat, planting, and destruction are not four names for one objective node. They are separate producers observed by quest logic:
- a stealth monitor records whether an optional rule was broken while the main activity continues;
- an encounter owns community activation, readiness, hostility, resolution, and cleanup;
- a plant interaction owns a real device action and completion signal before inventory is consumed;
- a destruction condition observes damage on an object that is already authored to be destructible.
Keep those producers separate and converge only their durable results.
start required activity
|-> arm optional stealth monitor -> stop and resolve outcome --\
`-> activate owner -> interact/fight/plant/destroy +
-> write required completion ---------------/
-> advance journal/reward state
-> delayed world cleanup
Prerequisites
Inventory every owner before editing the quest graph:
| System | Required authoring |
|---|---|
| Community | Registry, compiled area, named entries/phases, AI spots, character records, appearances, factions/attitudes, and activation policy |
| World | Quest prefab, combat-safe navigation, activation and cleanup volumes, device or destructible placement, bounds, and NodeRefs |
| Quest | Required and optional objectives, outcome facts, branch policy, reward/completion owner, and one-shot guard |
| Device/target | Controller class, supported action and condition function, persistent-state identity, interaction workspot, item record if consumed, and actual destruction capability if damage is required |
| Lifecycle | Spawn readiness, hostility target, nonlethal policy, wave end condition, late monitor signals, stream return, save/load, and cleanup |
Start with Communities and characters, Triggers and areas, Devices and persistent state, and Parallel monitors and cancellation.
Vanilla references
These focused base-game resources are useful comparisons. Extract them from your own installation; they are references, not files to copy into the project.
base\open_world\street_stories\westbrook\japantown\sts_wbr_jpn_03\phase\sts_wbr_jpn_03_streetstory.questphase
base\open_world\street_stories\westbrook\japantown\sts_wbr_jpn_03\phase\sts_wbr_jpn_03_combat.questphase
base\open_world\street_stories\watson\little_china\sts_wat_lch_01\phase\sts_wat_lch_01_combat.questphase
base\open_world\street_stories\santo_domingo\arroyo\sts_std_arr_05\phases\sts_std_arr_05_openworld.questphase
base\quest\main_quests\part1\q108\phases\q108_06b_tower_mainframe.questphase
base\open_world\street_stories\watson\northside_industrial_district\sts_wat_nid_03\phases\sts_wat_nid_03_openworld.questphase
base\open_world\phases\cyberpsychos\open_world_cyberpsychos.questphase
Optional stealth is a parallel monitor
An entry-time condition cannot represent “remain undetected while the player does something else.” Arm the optional objective, its failure wait, and its stop wait together:
activate optional objective
Out -> wait failure_fact > 0 -> objective Failed ------> race input 1
-> wait stop_fact > 0 -> objective Succeeded
-> set success_fact = 1 ---> race input 2
race output -> monitor child Out1
The reduced native graph uses these types:
| Role | Node/payload | Decisive properties |
|---|---|---|
| Start presentation | questJournalNodeDefinition | Objective path through gameJournalQuestObjective; enter through Active |
| Failure listener | questPauseConditionNodeDefinition → questFactsDBCondition → questVarComparison_ConditionType | failure_fact Greater 0 |
| Stop listener | Same types | stop_fact Greater 0 |
| Failure write | questJournalNodeDefinition | Same objective; enter through Failed |
| Quiet write | questJournalNodeDefinition | Same objective; enter through Succeeded |
| Durable quiet result | questFactsDBManagerNodeDefinition → questSetVar_NodeType | success_fact, setExactValue: 1, value: 1 |
| Convergence | questLogicalXorNodeDefinition | Two inputs, one output in the retained reduced shape |
The activity owner—not the monitor—produces stop_fact. Set it only after the
last gameplay action that is allowed to fail stealth. The security, sight,
combat, alarm, or script owner produces failure_fact.
XOR does not prove cancellation
An XOR-shaped convergence says where two routes meet. The focused vanilla graphs and reduced resource do not prove that the losing Pause Condition is destroyed, that simultaneous arrivals have stable priority, or that a late failure cannot emit.
Make the result safe even if the listener survives:
- Treat one outcome fact as authoritative.
- Gate every journal writer so only an unresolved monitor may write.
- Make late signals bypass all state mutation.
- If explicit cut topology is required, connect and test the exact
CutSource/CutDestinationtargets rather than assuming a phase exit cuts them. - Save with both waits armed, reload, and exercise both arrival orders.
Produce stealth failure from a real system
Choose a producer that corresponds to the player-facing rule. Examples include a security system entering combat, a dedicated detection fact, an alarm device state, or a named actor’s combat/senses condition. Do not set a failure fact merely because V entered the objective area.
For a security-driven failure, connect the security area to the encounter
community and inspect SecuritySystemControllerPS.IsSystemInCombat. Test that
the intended guards actually inform that security system.
For each producer, document:
| Question | Required answer |
|---|---|
| What arms it? | Community/device activation and stream boundary |
| What exact event or state fails stealth? | Controller function, fact writer, or character condition |
| Is it state-shaped or edge-shaped? | Determines activation and reload tests |
| When is it disarmed? | Stop fact, tested cut, or harmless late-signal policy |
| Who persists the result? | Dedicated fact and optional journal state |
Own the encounter lifecycle
A bounded community encounter follows this order:
activation trigger
-> activate objective/description/mappin
-> Spawn Manager Activate
-> wait required entries spawned
-> inject explicit hostile target per entry
-> wait for the authored resolution policy
-> succeed objective and set outcome
-> wait for safe cleanup boundary
-> Spawn Manager Deactivate
Activation and readiness
Whole-community Activate None/None is useful only when the encounter owns
every entry. Its Out socket does not prove every guard exists. An aggregate
CharacterSpawned Greater 0 with entireCommunity: 1 proves at least the
comparison’s community-scoped result, not every named entry.
When completion depends on a fixed roster, wait for each named entry or prove the exact whole-community comparison against a matching vanilla resource and runtime matrix. Start combat only after every required target has resolved.
Explicit hostility
The retained three-entry generated shape chains one combat command per actor:
questCombatNodeDefinition
entityReference
reference: <community NodeRef>
names: [<entry CName>]
function: questCombatNodeParams_ShootAt
params -> AIInjectCombatThreatCommandParams
dontForceHostileAttitude: 0
targetPuppetRef.reference: #player
duration: <authored value>
isPersistent: <authored value>
An attitude group, faction record, threat injection, and combat target are related but not interchangeable. A historical null-target pulse left visible guards passive; a later explicit target worked in its own candidate. Always name the actor receiving the command and the intended target, then test ordinary awareness, direct attack, player stealth, disengagement, and reload.
Completion is an outcome policy
questCharacterKilled_ConditionType is misleadingly broad. Its payload can
observe killed, defeated, and unconscious independently. A nonlethal-safe
encounter normally enables every outcome the objective accepts.
One retained reduced whole-community payload is:
| Property | Retained value | Boundary |
|---|---|---|
| Wrapper | questCharacterCondition | Character-condition schedule |
| Concrete type | questCharacterKilled_ConditionType | Name does not imply lethal-only behavior |
objectRef.reference | Encounter community NodeRef | No entry names in the whole-community shape |
comparisonParams.comparisonType | GreaterOrEqual | Coupled to the exact condition implementation |
comparisonParams.count | 0 | Do not reinterpret as generic arithmetic outside the source-matched shape |
comparisonParams.entireCommunity | 1 | Community-scoped result |
killed / defeated / unconscious | 1 / 1 / 1 | Accepts the three represented resolution states |
This is Structurally validated historical evidence, not a universal copy recipe. Inspect a vanilla encounter with the same roster and resolution rule, then prove that early deaths, initially absent entries, nonlethal takedowns, streaming, and save/load cannot satisfy or deadlock the gate incorrectly.
Compose waves as owned sub-lifecycles
Treat every wave as a complete sub-lifecycle rather than another Spawn Manager node appended to a shared condition:
wave 1: activate -> all required spawned -> hostility -> resolution -> cleanup
-> inter-wave fact/checkpoint/presentation
wave 2: activate -> all required spawned -> hostility -> resolution -> cleanup
-> encounter completion
Prefer separate communities or explicit named phases when later entries must not count toward the current wave. If several waves share one community, prove exactly how inactive, dead, despawned, or future entries affect every community-scoped comparison.
Sequential waves are Experimental. Test activation, engagement, resolution, cleanup, and the handoff between waves.
Do not spawn the next wave before the previous wave’s surviving actors have a defined owner. Decide whether survivors despawn, retreat, transfer into the next phase, or remain as harmless world state. A journal success edge alone does not clean them up.
Cyberpsycho encounters are a separate contract
Do not turn an ordinary three-guard graph into a cyberpsycho by renaming the objective. Focused vanilla investigation shows this broader lifecycle:
activate journal and area mappin
-> enter outer trigger
-> activate boss community and wait for named boss
-> keep boss protected until reveal/arena policy is satisfied
-> reveal from scan, sight, attack, hit, clue, or scripted event
-> enable gameplay AI, mortality, hostile target, and combat
-> wait broad resolution
-> split lethal and spared outcomes
-> evidence/report/reward
-> distance or exit cleanup
The minimum resource boundary includes a named boss community entry, combat
space, reveal and cleanup triggers, journal category/mappin, character record,
scanner/name data, and cleanup. The large boss health bar is driven by a
proper character record with rarity: NPCRarity.Boss plus hostile target
tracking; it is not a quest-node overlay toggle.
For resolution, use separate named-entry conditions:
| Outcome | killed | defeated | unconscious |
|---|---|---|---|
| Lethal | 1 | 0 | 0 |
| Spared/nonlethal | 0 | 1 | 1 |
Converge only after writing different outcome facts. The global
open_world_cyberpsychos.questphase owns umbrella progress; an individual
sighting still owns its own spawn, fight, report, reward, and cleanup.
These relationships are Observed in vanilla. A custom cyberpsycho encounter remains Experimental; test reveal, boss HUD, lethal/nonlethal resolution, reload, reporting, and cleanup.
Plant through a real device interaction
First decide whether the player or the quest initiates the operation. Those topologies are not interchangeable.
Player-driven runtime route
The retained quiet-install candidate used the device asset to expose a
player-selected Steal Data personal-link interaction. Its quest graph did
not send that initiating action. It waited for the resulting controller state,
then owned presentation and cleanup:
objective Active
-> device/template exposes the player prompt
-> PauseCondition: ScriptableDeviceComponentPS.IsPersonalLinkConnected
-> five-second install progress presentation
-> questInteractiveObjectManagerNodeDefinition
questDeviceManager_NodeType: QuestForceDisconnectPersonalLink
-> questItemManagerNodeDefinition: RemoveAll exact keylogger
-> objective Succeeded
-> completion fact
The decisive ownership is:
| Role | Owner or binding |
|---|---|
| Expose the initiating prompt | Device template, instance/controller package, interaction record, slot/workspot, and current persistent state |
| Observe connection | questPauseConditionNodeDefinition carrying questObjectCondition / questDevice_ConditionType, the exact device NodeRef and controller class, and IsPersonalLinkConnected |
| End the interaction | questDeviceManager_NodeTypeParams bound to the same device/controller and QuestForceDisconnectPersonalLink, after the wait and presentation |
| Consume item | questAddRemoveItem_NodeTypeParams, exact item TweakDBID, player reference, quantity/removal policy, and notification policy |
| Persist result | Dedicated fact set only after disconnection and inventory mutation succeed |
Send the disconnect action only after the connection wait has passed.
Quest-driven structural template
A separate reduced template serializes this order:
objective Active
-> quest sends the intended device action
-> wait for that action's authoritative controller condition
-> remove the exact item
-> objective Succeeded
-> completion fact
This command-before-condition template is Structurally validated, not runtime proof of the quiet-install route. Use it only when the quest itself is supposed to initiate the operation. If the player must select a prompt, expose the interaction through the device and begin the graph-side sequence at the resulting condition. A proximity trigger is not proof that either interaction occurred.
Device state is save-backed. Give a materially revised device a fresh identity during isolation, or a used save can preserve the preceding controller state. Test cancel, disconnect, repeated interaction, missing item, save during the interaction, completed reload, and candidate removal.
Observe destruction; do not synthesize it
The inspected sts_wat_nid_03_openworld.questphase contains this focused
condition twice:
questPauseConditionNodeDefinition
condition -> questObjectCondition
type -> questDestruction_ConditionType
objectRef.reference: #nid_03_ws_hwango_car_spawn_001
threshold: 100
That exact shape is Observed in vanilla. It proves a NodeRef-backed object and a serialized threshold can be observed. It does not prove:
- the units or scaling of
thresholdfor every object class; - that an arbitrary mesh, entity, or device has destructible components;
- which damage sources count;
- whether repair, stream return, or reload restores the target;
- whether two conditions on one target emit once or repeatedly.
First author or select a target whose entity/device contract already supports the intended damage and destruction. Then add the condition as an observer, not as the mechanism that makes the target destructible. A generic custom destructible-target tutorial remains Experimental until the target asset, condition, cleanup, and save matrix are retained together.
Manual WolvenKit composition order
- Build and inspect the community, security/device, and target resources before adding completion gates.
- Author activation and cleanup areas with correct notifier and outline buffers.
- Add objective and mappin activation, then community activation.
- Wait for every actor whose readiness matters; do not infer a fixed roster
from aggregate
> 0. - Add one explicit hostility command per intended actor and target.
- Add the exact lethal/nonlethal or protected-target resolution policy.
- If stealth is optional, split the failure and stop listeners from the same start edge and make late signals harmless.
- If planting is player-driven, expose the device prompt, wait for its controller state, then perform presentation, cleanup action, inventory mutation, and the completion fact. If it is quest-driven, send an initiating action only when that behavior is explicitly intended and separately tested.
- If destruction is required, verify the target’s destructible behavior independently before trusting the quest condition.
- Advance journal/reward state before delayed cleanup. Do not let an encounter cleanup edge erase a scene actor or escorted target owned by the next phase.
- Serialize and reopen every CR2W with WolvenKit
8.19.0, inspect handles and NodeRefs, pack, extract-verify, and freeze exact installed build.
Controlled acceptance matrix
| Case | Required observation |
|---|---|
| Quiet stealth route | Optional objective succeeds once; required activity remains completable |
| Detection route | Correct producer fails stealth once; required activity still follows its authored policy |
| Late failure after stop | No second journal write or contradictory fact |
| Save with monitor armed | Reload preserves one valid outcome route without duplicate listeners |
| Encounter approach | No actors before activation; every required actor spawns once on safe navigation |
| Lethal completion | Accepted lethal states complete exactly once |
| Nonlethal completion | Accepted incapacitated states complete exactly once; rejected states do not |
| Partial roster | One unresolved or unstreamed entry cannot falsely complete the encounter |
| Wave transition | Previous wave ownership ends before the next wave becomes authoritative |
| Plant cancel/retry | Cancel does not consume the item or write completion; retry can succeed |
| Destruction thresholds | Below/at/above boundary behave exactly as the design relies on |
| Stream away/return | Actors, device, target, and armed waits restore without duplicates |
| Completed reload | No objective, hostility pulse, reward, or cleanup repeats |
| Clean replay | Both stealth outcomes and every required combat outcome work from separate untouched saves |
For cyberpsycho work, add approach, reveal, arena, hostile acquisition, boss HUD, lethal, spared, evidence, report, reward, cleanup, and saves at each hold point. Test each transition in game.
Common failures
| Symptom | Inspect first |
|---|---|
| Optional stealth resolves immediately | Stop/failure facts inherited from the save, entry-time test used instead of a monitor, or wrong comparator |
| Both stealth outcomes write | Losing listener still active, no authoritative outcome fact, or simultaneous signal policy untested |
| Guards spawn but remain passive | Missing explicit target, wrong named entry, faction/attitude mismatch, threat command before readiness |
| Encounter completes early | Aggregate readiness/resolution scope, future/inactive entries, count/comparator payload, stale facts |
| Encounter never completes nonlethally | defeated/unconscious flags, actor identity, protected/invulnerable state, or wrong target roster |
| Second wave never appears | Previous community not cleaned/transferred, next sector not streamed, reused phase state, or shared comparison counting the wrong entries |
| Plant objective completes on approach | Proximity used as completion instead of device condition |
| Plant action works only once across builds | Save-backed device state or reused NodeRef/controller persistence |
| Damage never advances objective | Target is not destructible, wrong object NodeRef, wrong threshold semantics, or damage source not supported |
| Actor disappears during cleanup | Deactivation occurs before journal/scene/escort ownership transfers |
Use Serialization success versus runtime validity when a round-tripped resource still fails in game, and Controlled isolation and evidence before changing several lifecycle surfaces at once.
Rescue, escort, defend, and carry
Extraction objectives are lifecycle chains, not one “follow NPC” node. A quest may release a target, transfer it into gameplay AI, observe ordered movement, preserve it through combat, wait for a carry or trunk state, and only then clean up. Each transition changes which system owns the actor.
community owns inactive or restrained target
-> quest/device releases target
-> quest proves named target readiness
-> gameplay AI or follower role owns movement
-> ordered destination gates prove progress
-> defend/carry/trunk activity owns the next state
-> durable quest outcome is written
-> AI role, community, device, marker, and actor cleanup complete
Do not deactivate and respawn the actor at every stage boundary. Preserve one identity and transfer ownership deliberately.
Prerequisites
Prepare these resources before editing the child phases:
| Owner | Required material |
|---|---|
| Target community | Registry, compiled area, named target entry/phase, character record, appearance, AI spot, activation/readiness policy, and long enough streaming bounds |
| Release device | Placed device, controller class, exact action and completion function, device registry entry, interaction/workspot, and fresh persistent identity for materially changed tests |
| Route | Walkable navigation, three or more deliberate destination volumes, route/final markers, bounds, and recovery policy |
| Defend encounter | Protected-target identity, attacker community/waves, hostility targets, survival or completion producer, failure policy, and cleanup |
| Carry/trunk | Carry-capable target state, destination volume, vehicle identity if used, trunk interaction owner, and post-placement cleanup |
| Quest | Objectives, descriptions, mappins, outcome facts, checkpoint/retry policy, one-shot guard, and terminal success/failure state |
Read Activation, readiness, and acquisition, Cleanup and character safety, and Immediate branches and waiting gates first. Route volumes use the world contract in Triggers and areas.
Vanilla references
These focused base-game resources are useful comparisons. Extract them from your own installation; they are references, not files to copy into the project.
base\open_world\street_stories\watson\northside_industrial_district\sts_wat_nid_03\phases\sts_wat_nid_03_gameplay.questphase
base\open_world\street_stories\westbrook\japantown\sts_wbr_jpn_09\phases\sts_wbr_jpn_09_gameplay.questphase
base\open_world\street_stories\heywood\vista_del_rey\sts_hey_rey_09\phases\sts_hey_rey_09_openworld.questphase
base\open_world\street_stories\watson\little_china\sts_wat_lch_01\phase\sts_wat_lch_01_combat.questphase
base\quest\side_quests\sq004\phases\sq004_03_raffen_shiv_camp.questphase
base\open_world\street_stories\watson\kabuki\sts_wat_kab_02\phases\sts_wat_kab_02_openworld.questphase
Plan the actor ownership timeline
inactive community
-> restrained target (activate and spawn)
-> released target (authoritative action confirmed)
|-> following -> final hold -> defended -> cleaned
| `-> lost -> retry or terminal failure
`-> carried -> delivered by destination/trunk state -> cleaned
Write down the owner at every state. A scene, follower role, workspot, combat command, carry interaction, and vehicle trunk can all hold references to the same actor. Cleanup is safe only after the current owner has released it and the quest has persisted the outcome.
Release or rescue a named target
A bounded native release child can use this ordered graph:
objective Active
-> send release/unlock device action
-> wait matching device controller function
-> wait named target CharacterSpawned
-> objective Succeeded
-> set released fact
-> Out1
The decisive types and joins are:
| Role | Native shape | Required identity |
|---|---|---|
| Send action | questInteractiveObjectManagerNodeDefinition → questDeviceManager_NodeType | objectRef, deviceControllerClass, deviceAction, and action properties all match the placed device |
| Confirm action | questPauseConditionNodeDefinition → questObjectCondition → questDevice_ConditionType | Same device NodeRef/controller plus exact deviceConditionFunction and parameters |
| Confirm target | questPauseConditionNodeDefinition → questCharacterCondition → questCharacterSpawned_ConditionType | Community NodeRef plus exact target entry name, entireCommunity: 0, and the intended comparison |
| Persist release | questFactsDBManagerNodeDefinition → questSetVar_NodeType | Quest-local released fact written exactly once |
This child does not create or activate the target community. An earlier owner must activate it and prove the target exists. It also does not assign follower AI. A successful device condition should hand off to readiness, and readiness should hand off to the movement owner.
For a non-device rescue, replace only the producer: a scene exit, combat resolution, door state, or scripted fact may authorize release. Keep the same requirements—one authoritative completion signal, named target readiness, a durable result, and explicit lifecycle transfer.
Device state persists in saves. A save that saw an earlier controller or NodeRef may make a corrected action appear already complete. Use an untouched save lineage and a fresh identity when isolating a materially changed device.
Assign gameplay AI before observing movement
The reduced escort shape first promotes the target to gameplay AI:
questPuppetAIManagerNodeDefinition
entries[]
aiTier: Gameplay
entityReference
reference: <target community NodeRef>
names: [<target entry CName>]
It then assigns a player-following role through a
questMiscAICommandNode whose typed params contain:
AIAssignRoleCommandParams
role -> AIFollowerRole
followerRef.reference: #player
The actor reference on the command must select the same community/entry. The role makes the target a follower; it does not author navmesh, a patrol spline, doors, elevators, combat recovery, teleport catch-up, or route markers.
Test the follower before adding route gates:
- target begins in the intended released state;
- gameplay AI and follower role apply once;
- target follows through ordinary walkable space;
- target responds to combat as intended;
- stream-away/return and save/load preserve one valid role;
- role clear returns the target to an intentional phase or cleanup state.
Observe the NPC, not the player, at route gates
An escort gate uses the named actor as the trigger activator:
questPauseConditionNodeDefinition
condition -> questTriggerCondition
triggerAreaRef: <destination NodeRef>
type: IsInside
isPlayerActivator: 0
activatorRef
reference: <target community NodeRef>
names: [<target entry CName>]
isPlayerActivator: 0 is decisive. Waiting for the player would allow V to
finish the escort while the target remained behind.
A clear three-stage route is:
activate route pin 1 -> wait target in gate 1 -> clear pin 1
activate route pin 2 -> wait target in gate 2 -> clear pin 2
activate route pin 3 -> wait target in gate 3 -> clear pin 3
Keep every trigger and its current marker streamable before it becomes the active gate. Historical testing found a route where only the final marker kept the final trigger relevant while the graph was waiting on the first gate. The fixed candidate gave all three gates adequate world coverage and advanced in order.
Trigger placement is three-dimensional. Center the volume around the actor’s walkable plane; do not place its base exactly at ground height and assume the actor will count as inside. Inspect the authoritative outline buffer, node transform, height, bounds, and notifier.
Transfer the follower role at the final gate
The final gate is an ownership boundary, not automatically the end of the actor’s lifecycle. Choose one explicit result:
| Next activity | Final-gate action |
|---|---|
| Quest ends and actor should leave | Clear follower role, move to a safe community phase if needed, then delay deactivation |
| Defend target | Retain follower/gameplay ownership through the hold; clear only after success or terminal failure cleanup |
| Scene | Move to the required scene/workspot state, wait readiness, let the scene acquire, and clear only after its named exit |
| Continue escort | Preserve role and transfer to the next route phase without deactivation |
Clearing the follower role at the final escort gate can let the actor return to its community AI spot during the next activity. Retain the role through a defend hold when the target must remain with V, then clear it during the chosen success or failure cleanup.
Defend with an explicit success/failure race
A defend objective needs two independent producers:
- success: timer elapsed, attackers resolved, scripted work completed, or a dedicated encounter fact;
- failure: protected target killed, defeated, unconscious, below a health threshold, or otherwise in the exact prohibited state.
A compact fact-backed shape is:
objective Active
Out -> wait completion_fact > 0 -> objective Succeeded -> race input 1
-> wait protected target lost -> objective Failed
-> set failure_fact -> race input 2
race output -> Out1
The target-loss listener uses a named entity reference:
questCharacterKilled_ConditionType
objectRef.reference: <protected community NodeRef>
objectRef.names: [<protected entry CName>]
comparisonParams.entireCommunity: 0
killed / defeated / unconscious: <failure policy>
Set the three outcome flags deliberately. A protected target that may be incapacitated without failing needs a different payload from one where any defeat is terminal.
Spawn the attack before starting survival time
The retained successful hold followed this sequence:
activate attacker community
-> wait attackers spawned
-> inject two threats against protected actor and one against V
-> arm target-loss listener and 20-second timer
-> timer sets completion fact
-> success deactivates surviving attackers
-> clear follower role
-> persist terminal success
Starting the timer before attackers are ready measures streaming latency, not survival. For a kill-all defense, replace the timer producer with the exact accepted attacker-resolution policy. For multiple waves, make each wave an owned activation/readiness/combat/resolution/cleanup sub-lifecycle; see Stealth, combat, and destruction.
Retry is a separate policy
A researched structural variant deliberately does not connect its failure
branch to Out1. It is intended to sit after a parent checkpoint whose
questCheckpointNodeDefinition uses the chosen retry policy. This makes
failure block normal progression instead of converging with success.
That topology is Structurally validated, but generic checkpoint reload, fact rollback, attacker restoration, target restoration, journal reset, and repeat reward behavior are Experimental. Do not label “retry-safe” until the exact candidate passes:
- failure before and after attackers engage;
- manual reload of the pre-defense checkpoint;
- automatic retry behavior if the design relies on it;
- clean restoration of target health/state and follower role;
- no duplicated attackers, timers, facts, notifications, or rewards;
- repeated failure followed by success;
- completed-save reload.
An XOR-shaped join also does not prove cancellation of the losing timer or target-loss listener. Make late signals harmless or test explicit cut topology.
Carry requires two simultaneous truths
The reduced carry child first waits until the named target is mounted to the player:
questPauseConditionNodeDefinition
condition -> questCharacterCondition
type -> questCharacterMount_ConditionType
condition: OnMount
childRef.reference: <target community NodeRef>
childRef.names: [<target entry CName>]
childIsPlayer: 0
parentIsPlayer: 1
anyChild: 0
anyParent: 0
It then waits on a logical AND containing:
- the same target-still-mounted condition; and
- a player
IsInsidecondition for the delivery trigger.
target mounted to V
-> wait (target still mounted to V AND V inside destination)
-> objective Succeeded
This shape prevents a prior mount event from completing after the body has
been dropped elsewhere. It is Structurally validated and comparable to
the character-mount shapes Observed in vanilla in
sts_hey_rey_09_openworld.questphase. Custom carry behavior remains
Experimental.
The graph does not create the incapacitated state, pickup prompt, carry animation, player locomotion restrictions, drop action, destination presentation, or actor cleanup. Those are prerequisites and separate acceptance surfaces.
Trunk placement is not the carry destination condition
The same inspected vanilla phase contains a
questVehicleTrunk_ConditionType with a more specific identity join:
questVehicleTrunk_ConditionType
isInside: 1
inverted: 0
anyObject: 0
anyVehicle: 0
playerVehicle: 0
objectRef
reference: <target community NodeRef>
names: [<target entry CName>]
vehicleRef
reference: <vehicle community NodeRef>
names: [<vehicle entry CName>]
That shape is Observed in vanilla. It proves the condition can name both the object and vehicle. It does not author the trunk interaction, accept an arbitrary car, make an actor carryable, or clean up either entity.
A trunk-delivery route should therefore be:
carry target to vehicle
-> vehicle/trunk interaction owns placement
-> wait exact object-inside-exact-vehicle condition
-> persist delivery
-> release carry/trunk interaction ownership
-> delayed actor and vehicle cleanup
If the design permits any player vehicle, research and test that variant
instead of changing playerVehicle/anyVehicle by intuition. Generic trunk
placement remains Experimental.
Workspot arrival is another distinct state
An escort often ends at a workspot rather than a trigger. The focused vanilla corpus contains:
questCharacterWorkspot_ConditionType
puppetRef.reference: <community NodeRef>
puppetRef.names: [<entry CName>]
spotRef: <AI spot NodeRef>
animationName: None
waitForAnimEnd: 0
isPlayer: 0
This is Observed in vanilla only. The condition observes a puppet/spot relationship; it does not move the actor into the spot, prove workspot compatibility, or guarantee animation quality. The community phase, AI command, scene, or other movement owner must send the actor there first.
Use a workspot gate only when the story requires that state. A broad final trigger is more tolerant of navmesh and animation differences; a workspot condition is more precise but adds asset, role, and animation dependencies.
Manual WolvenKit composition order
- Build the target community and release device. Prove activation, named spawn, action, and completion function independently.
- Add release objective/action/condition/readiness/fact in one ordered child.
- Set gameplay AI for the same named entity, then assign the intended follower role.
- Place route volumes on verified navigation, give each current gate adequate
streaming coverage, and add NPC-activator
IsInsidewaits in order. - Activate and clear route mappins around their matching waits.
- At the final gate, transfer the follower role to the defend, scene, carry, or cleanup owner instead of clearing it by habit.
- For defense, activate and wait for attackers before arming the timer or completion producer; add a separate named target-loss listener.
- Persist success/failure before community or role cleanup. Keep reward and terminal quest completion on a one-shot route.
- For carry, wait target-on-player first, then wait the logical AND of target-still-mounted and player-inside-destination.
- For trunk or workspot completion, use the focused condition only after the interaction/movement owner has been authored and tested.
- Serialize and reopen every CR2W with WolvenKit
8.19.0, verify handles, actor references, paths, and sockets, pack, extract-verify, and freeze the installed filenames.
Clean-save and lifecycle matrix
Use independent descendants of an untouched pre-quest save. Record the exact archive, framework files, versions, and save-slot history for every case.
| Case | Required observation |
|---|---|
| Before activation | Target and attackers follow their authored inactive policy |
| Release cancel/failure | No released fact, no premature objective success, retry remains possible |
| Release success | Device state, named readiness, objective, and fact advance once |
| Escort ordinary route | Target—not V—crosses every gate in order; pins follow the current gate |
| Player outruns target | Later gate cannot complete early; target recovers or follows documented failure policy |
| Combat during escort | Follower role, target survival, and return to route are correct |
| Save between every gate | Reload restores one target, one current pin, and the correct next gate |
| Defend success | Attackers spawn before timing, success wins once, survivors and role clean up safely |
| Defend failure | Target outcome, journal state, failure fact, attacker cleanup, and retry/terminal policy are exact |
| Near-simultaneous defend signals | Only the intended result mutates state; late losing signal is harmless |
| Carry pickup/drop/re-pickup | Mount condition follows the actual target state and cannot complete from history alone |
| Carry destination | Target remains mounted while V is inside; completion and cleanup occur once |
| Trunk placement/removal | Exact object/vehicle pair toggles the observed state; wrong vehicle cannot satisfy it |
| Workspot arrival/exit | Condition follows the intended actor and spot; interruption does not deadlock cleanup |
| Stream away/return | Target, current role, route gate, attackers/device, and objective restore without duplicates |
| Completed reload | No release action, follower assignment, wave, timer, reward, marker, or cleanup replays |
| Removal isolation | Removing the exact candidate does not masquerade save-backed state as current behavior |
Carry, trunk placement, workspot transitions, and failure/retry remain Experimental. Test each transition and its cleanup separately.
Common failures
| Symptom | Inspect first |
|---|---|
| Release action fires but actor stays restrained | Controller action/function pair, same device NodeRef, target community phase, readiness identity |
| Actor appears but does not follow | Gameplay AI tier, assign-role params, target community/entry, follower ref, navmesh |
| Gate advances when V arrives alone | isPlayerActivator, empty/named activatorRef, wrong trigger consumer |
| First gate never advances | Volume vertical placement, notifier, target identity, streaming bounds, target on valid navigation |
| Actor walks back at final gate | Follower role cleared before the next owner, persistent community AI returning to its spot |
| Defense timer completes before combat begins | Timer armed before attacker readiness and threat injection |
| Defense succeeds and then fails | Losing listener still active, no authoritative outcome, untested XOR/cut semantics |
| Retry duplicates attackers or rewards | Checkpoint boundary, persistent facts/journal, active listeners, cleanup, one-shot guard |
| Carry completion survives dropping the target | Historical mount event used without a current mount-and-destination AND gate |
| Trunk condition accepts wrong vehicle | anyVehicle, playerVehicle, or vehicle entity reference too broad |
| Workspot wait never resolves | Movement owner never assigned the spot, wrong spot NodeRef, incompatible actor/workspot, interrupted AI role |
| Cleanup visibly pops the target | Deactivation before scene/AI/carry/trunk owner releases it or cleanup area too small |
When a corrected resource still behaves like an older route, audit Save state and clean retests. When an actor exists but an identity join fails, use Actors, scenes, and lipsync together with the Identifier-domain reference.
Mount, ride, drive, and theft
Vehicle objectives join a quest graph to an already authored vehicle. The graph can wait for V or another character to mount it, assign a character to a seat, observe the vehicle inside a trigger, update Journal presentation, and preserve an outcome fact. It does not create the vehicle’s community entry, placement, driving AI, road route, or cleanup policy.
Tested with Cyberpunk 2077 2.31a, WolvenKit 8.19.0, ArchiveXL 1.27.0,
RED4ext 1.30.0, and redscript 0.5.31. See Tested
versions.
The node shapes below are Observed in vanilla. A new vehicle lifecycle remains Experimental until mount, passenger assignment, arrival, theft, delivery, cleanup, and reload have been tested together.
Name the vehicle before building the graph
A vehicle reference can use different identity domains. Record which one the chosen node expects.
| Identity | Example consumer | What must exist |
|---|---|---|
| Community NodeRef plus entry CName | questCharacterMount_ConditionType, questAssignCharacter_NodeType, a vehicle-as-activator trigger | A streamed community registry, vehicle entry, spawn set, and placement that resolve together |
| Direct world NodeRef | A trigger or condition whose gameEntityReference.reference names one placed vehicle | A stable placed entity in the owning prefab/sector scope |
| Player-vehicle TweakDB record | questEnablePlayerVehicle_NodeType | A player-vehicle record and the player-vehicle system; not an arbitrary world vehicle |
| Scene actor | Vehicle staging owned by a scene | A declared scene actor and scene-local acquisition contract |
Do not put a community NodeRef into names, an entry CName into reference,
or a TweakDB record into either field. See Identifier
domains and Quest prefabs and
NodeRefs.
For a community-owned vehicle, a typical entity reference has this semantic shape:
| Field | Value domain |
|---|---|
reference | NodeRef of the community registry or compatible entity owner |
names | one CName selecting the vehicle entry |
type | EntityRef |
dynamicEntityUniqueName | normally None unless that identity route was deliberately authored |
The same pair must be used by mount, assignment, arrival, and cleanup logic. Changing only one consumer creates two apparently plausible identities for one vehicle.
Recipe: V enters a named vehicle
Use this shape when completion means that V mounts the designated vehicle. It does not prove that V is the driver unless the role and seat contract require that state.
In1
-> objective Active
-> vehicle mappin Active
-> wait: V OnMount designated vehicle
-> objective Succeeded
-> vehicle mappin Inactive
-> Out1
The waiting node is a questPauseConditionNodeDefinition whose condition is a
questCharacterCondition carrying a
questCharacterMount_ConditionType.
| Mount field | Focused value | Meaning |
|---|---|---|
condition | OnMount | Wait for the mount event/state represented by this condition family |
childIsPlayer | 1 | The mounted child is V |
parentRef | the exact vehicle entity reference | The designated parent vehicle, not any nearby car |
anyParent | 0 | Keep the parent bound to that reference |
anyChild | 0 | Do not broaden the child match |
role | choose deliberately | Driver, Passenger, or a tested neutral role must match the intended objective |
vehicleOrigin, vehicleType | only broaden when intended | Any is not a substitute for the exact parentRef |
The focused reduced candidate used a neutral role while the vanilla driving comparison contains role-aware variants. If the objective says “drive,” test the driver-seat condition; a neutral mount condition can also succeed when V enters as a passenger.
Observed in vanilla: extract
base\quest\side_quests\sq004\phases\sq004_02_drive.questphase and inspect
only its mount conditions, vehicle references, and nearby objective flow.
Those nodes remain bound to sq004 identities and are not a copy-ready child
phase.
Recipe: V rides with a contact
“Ride with” has two independent facts: the contact occupies the intended seat and V occupies the same vehicle. A single mount wait cannot prove both, while two latched signals do not automatically prove both remain true together.
-> wait: V mounted as intended -----------\
In1 -> objective -> assign contact to vehicle seat AND -> objective Succeeded -> Out1
-> wait: contact mounted as passenger ----/
- Activate the objective.
- Use
questVehicleNodeDefinitionwithquestAssignCharacter_NodeTypeto bind the contact to the vehicle and seat. - Fan out to one player mount condition and one contact mount condition.
- Join them with
questLogicalAndNodeDefinition; use two distinct input sockets. - Succeed the objective only after the join fires.
That graph-level AND is only a structurally valid signal join. Its
arrival-memory, unmount, cancellation, and reload semantics remain
Experimental. If the objective requires both actors to occupy the vehicle
at the same boundary, use one waiting questLogicalCondition whose AND tree
contains both current mount predicates, or immediately recheck both current
states before success. Retain an adversarial test where the first actor
unmounts before the second mounts; test this ordering explicitly.
The assignment payload needs four identities plus explicit operation policy:
| Property | Owner |
|---|---|
characterRef.reference | contact community NodeRef |
characterRef.names | contact entry CName |
vehicleRef.reference | vehicle community or entity NodeRef |
vehicleRef.names | vehicle entry CName |
assign | 1 to assign the contact; 0 is a clear/unassign operation, not an equivalent default |
isPlayer | 0 when characterRef names the contact; 1 changes the subject to V |
slotName | a seat slot supported by the vehicle, such as a tested passenger slot |
isInstant | staging policy; the exact passing candidate used 1, which is not a visible enter animation |
For a front-right passenger assignment, inspect whether the vehicle supports
seat_front_right. Use isInstant: 1 only when instant staging is intended;
it does not request a visible entry animation.
Assignment is not readiness. Wait for the contact community and vehicle to be streamed and spawned before issuing it, then retain the contact mount condition. A fast approach can otherwise expose the same readiness class described in Activation, readiness, and acquisition.
Observed in vanilla: compare the player/contact mount conditions and
questAssignCharacter_NodeType work in
base\quest\side_quests\sq004\phases\sq004_02_drive.questphase. The reduced
candidate proves only a structurally valid assignment plus two-way join; it
does not provide a road spline, driving command, scene, or interruption route.
Recipe: drive a named vehicle into an area
The arrival trigger must observe the vehicle, not V. Reusing a player-area condition completes when V crosses the boundary on foot.
In1
-> objective Active
-> destination mappin Active
-> wait: designated vehicle IsInside destination trigger
-> objective Succeeded
-> destination mappin Inactive
-> set completion fact = 1
-> Out1
Use a questTriggerCondition with:
| Field | Required contract |
|---|---|
triggerAreaRef | NodeRef of the placed destination trigger |
activatorRef | exact vehicle entity reference |
isPlayerActivator | 0 |
type | IsInside |
This is arrival, not delivery. If the vehicle must also stop, continue with the speed condition in Vehicle delivery, cleanup, chase, and race. If V must remain inside, add a separate mount condition and join it explicitly.
Recipe: record theft by mount
A minimal theft objective is the enter-vehicle recipe followed by a persistent outcome fact:
objective Active -> mappin Active -> V OnMount vehicle
-> objective Succeeded -> mappin Inactive
-> theft fact = 1 -> Out1
The fact records the quest’s interpretation of the event. It does not change vehicle ownership, player-garage availability, police response, lock state, or despawn behavior. Those systems need separately authored and tested resources.
Use distinct facts for “mounted,” “delivered,” and “cleaned” when later logic needs to diagnose partial progress. One overloaded completion fact makes reload repair ambiguous.
Test the complete sequence with the designated vehicles: V mounts the contact vehicle, the passenger takes the correct seat, the vehicle arrives, V mounts the theft vehicle, and that same vehicle reaches delivery before cleanup.
Manual WolvenKit composition order
- Extract the cited
sq004_02_drive.questphasefrom your own game and inspect only its mount, assignment, and vehicle-activator fields. Record its graph scope; do not copy the phase or its identities. - Author or select the vehicle’s actual owner: community/spawn set, placed world entity, player-vehicle record, or scene. Give the mod-owned vehicle, contact, prefab, and NodeRefs unique identities.
- Verify the vehicle and any contact resolve and become spawned before adding assignment or mount waits.
- In the child phase, activate the intended objective and mappin, then add one
questPauseConditionNodeDefinitionper player/contact mount truth. - Bind every
gameEntityReferencewith the exact community/world NodeRef and entry name expected by that condition. Choose the player role and passenger seat deliberately. - Add
questAssignCharacter_NodeTypeonly after readiness. When simultaneous occupation is required, prefer a Boolean condition tree or an explicit current-state recheck over a graph join that can remember old signals. - For arrival, place a destination trigger and bind the exact vehicle as its non-player activator. Keep vehicle arrival separate from player presence and stopped-delivery semantics.
- Succeed presentation, remove mappins, and write distinct mount/arrival/theft facts before handing off to delivery or cleanup.
- Reopen the phase and inspect handles, sockets, NodeRefs, entry names, seat
slots, roles, and journal paths. Round-trip the mod-owned resources with
WolvenKit
8.19.0. - Pack one exact candidate and run the controlled cases below from separate copies of an untouched pre-install save.
Presentation and lifecycle checklist
Before connecting the child phase, confirm:
- the objective and map-pin paths resolve to the intended entries and share
the correct
fileEntryIndex; - the map pin points to the vehicle or destination that the condition uses;
- the parent phase owns every prefab needed to resolve vehicle, community, and trigger NodeRefs;
- the vehicle and contact have explicit activation and spawned-readiness gates;
- completion inactivates presentation without erasing facts needed for later stages;
- interruption says whether the contact unmounts, the vehicle remains, and the objective fails or becomes inactive;
- another child owns delivery, cleanup, or handoff instead of silently assuming it happened.
Controlled runtime cases
Run these cases in separate save slots after the candidate is structurally validated:
| Case | Start state | Expected observation |
|---|---|---|
| Normal mount | Vehicle and contact not previously activated | Correct vehicle resolves; only the intended seat/state completes |
| Wrong vehicle | Another vehicle is beside the target | Mounting the other vehicle does not advance |
| Passenger versus driver | V changes seats before the wait | The declared role behaves exactly as documented |
| First actor unmounts early | Contact or V leaves before the other mount truth occurs | A simultaneous-occupancy objective does not complete from two historical signals |
| Fast approach | Load just outside streaming range and approach quickly | Readiness gates prevent lost assignment or mount waits |
| Reload before mount | Objective and mappin active | The same vehicle identity and wait resume |
| Reload after fact | Theft/arrival fact already 1 | Parent re-entry policy skips or resumes intentionally |
| Stream away and return | Vehicle remains quest-relevant | Community, entry, marker, and condition still resolve |
| Removal isolation | Candidate archive and loose registrations removed on a dedicated copy | No save-backed state is mistaken for installed content |
Vehicle and device state can be save-backed. Follow Save state and clean retests before changing a NodeRef or entry name and declaring the edit causal.
Vehicle delivery, cleanup, chase, and race
Delivery is a small condition chain. Cleanup is an ownership decision. Chases and races are large authored systems with vehicles, occupants, splines, world-space gates, UI, recovery, and failure handling. Treating all four as “vehicle nodes” hides the boundaries most likely to break.
Tested with Cyberpunk 2077 2.31a, WolvenKit 8.19.0, ArchiveXL 1.27.0,
RED4ext 1.30.0, and redscript 0.5.31. See Tested
versions. The stopped-delivery chain below is
a new composition and needs its own in-game test.
Recipe: record arrival, then a later stop
A focused delivery child waits for the exact vehicle to enter the destination, then waits for that same vehicle to stop:
In1
-> objective Active
-> wait: designated vehicle IsInside destination trigger
-> wait: designated vehicle speed == 0
-> objective Succeeded
-> Out1
The first wait is a questPauseConditionNodeDefinition containing a
questTriggerCondition. Its activatorRef is the vehicle,
isPlayerActivator is 0, triggerAreaRef is the destination NodeRef, and
type is IsInside.
The second wait contains questVehicleCondition with
questVehicleSpeed_ConditionType:
| Field | Focused value |
|---|---|
vehicleRef | the same exact entity reference used by the trigger |
comparisonType | CT_EQUAL in the reduced candidate |
speed | 0 |
This sequential topology proves only “the vehicle entered earlier and stopped
later.” Once IsInside emits, that wait is retired. The vehicle can leave the
destination and satisfy the speed wait somewhere else. Do not read this graph
as “stopped inside,” or speed == 0 as “parked correctly.” It also does not
prove orientation, damage state, engine state, whether V exited, or whether the
vehicle belongs to the intended delivery stage.
Require stopped and inside at the same boundary
When simultaneous containment is the promise, author one waiting predicate whose Boolean tree requires both current states:
PauseCondition
questLogicalCondition AND
|-> questTriggerCondition: exact vehicle IsInside destination
`-> questVehicleCondition: same vehicle speed == 0
Alternatively, re-evaluate IsInside immediately after the speed wait and
route false back to arrival. An AND that remembers two earlier signals has
the same temporal defect as the sequential chain. The combined predicate is
Experimental; test already-true, exit/re-entry, reload, and both event orders.
Observed in vanilla: inspect
base\quest\side_quests\sq004\phases\sq004_02_drive.questphase for an exact
vehicle activatorRef in IsInside triggers and a
questVehicleSpeed_ConditionType using CT_LESS_EQUAL with speed 2 for that
vehicle. This is a close low-speed precedent, not the candidate’s exact
CT_EQUAL/0 pair. As a second contrast,
base\open_world\street_stories\watson\northside_industrial_district\sts_wat_nid_02\phases\sts_wat_nid_02_gameplay.questphase
contains CT_NOT_EQUAL/0 for its van while its nearby triggers are
player-activated. Do not merge those separate observations into one vanilla
delivery chain.
The sequential chain expresses arrival followed by a stop. Use the combined predicate or recheck containment when delivery requires stopping inside.
Separate delivery from handoff
Most delivery stories need more than the condition chain:
vehicle satisfies the exact delivery predicate
-> capture delivery fact
-> ask V to exit, if required
-> transfer or lock vehicle, if authored
-> run recipient scene or message
-> settle reward/outcome
-> remove marker
-> perform owner-specific cleanup
Each arrow is a separate claim. In particular, a recipient scene does not transfer ownership, and a completion fact does not despawn the vehicle. Keep the objective active until the last player-facing requirement that its text actually promises.
Cleanup starts with ownership
Choose cleanup from the way the vehicle entered the activity.
| Vehicle source | Likely owner | Safe question to answer |
|---|---|---|
| Community/spawn-set vehicle | Community and its world placement | Which phase deactivates the entry or community, and after what distance/readiness gate? |
| Placed world vehicle | Sector/prefab plus quest lifecycle | Should it persist, stream naturally, or receive a resource-specific disable path? |
| Player-vehicle record | Player-vehicle system | Is questEnablePlayerVehicle_NodeType valid for this exact TweakDB record and state? |
| Scene-staged vehicle | Scene plus surrounding quest | Which exit owns unmount, visibility, and post-scene handoff? |
questEnablePlayerVehicle_NodeType operates on player-vehicle records; it is
not a generic world-entity despawner. Target the exact record and delay cleanup
until no later activity needs that vehicle.
Observed in vanilla: extract
base\quest\side_quests\sq031\phases\sq031_porsche.questphase and inspect the
exact questEnablePlayerVehicle_NodeType context, record domain, incoming
state, and following nodes. Do not substitute a community entry or world
NodeRef for its vehicle record.
For community actors and vehicles, begin with Cleanup and character safety: stop dependent work, deactivate at a safe boundary, and retest stream-away/return. Precise cleanup for a newly authored vehicle remains Experimental until the exact owner and save behavior pass in game.
Chase is a coordinated encounter
A chase needs a separate encounter design. Define pursuit, recovery, escape, and failure behavior before wiring the vehicle conditions.
- spawned vehicles and occupants with consistent community/entry identities;
- seat assignment and spawned-readiness gates;
- movement or combat AI with a target and recovery policy;
- start, success, lost-target, damage, death, and interruption signals;
- streaming coverage along the route;
- player-facing objectives and mappins that survive detours and reloads;
- cleanup for every vehicle, occupant, monitor, and restriction.
Observed in vanilla: the installed
base\open_world\street_stories\badlands\inland_avenue\sts_bls_ina_07\phases\sts_bls_ina_07_gpl.questphase
contains named chase facts, multiple questVehicleNodeDefinition assignment
operations that bind occupants to vehicle seats, AI patrol roles, and
conditions for lost/route state. That observation is evidence of a composite,
not an isolated reusable chase recipe.
Use the vanilla phase as a question map:
- Which resource spawns each pursuer vehicle and occupant?
- Which node assigns each character to which seat?
- Which AI command begins movement or combat?
- Which facts distinguish route and lost-target states?
- Which branch owns success, failure, and cleanup?
If those answers span several external phases or world resources, preserve the split. Collapsing them into one guessed child discards lifecycle ownership.
Race is a subsystem, not a trigger loop
Observed in vanilla: the installed
base\quest\side_quests\sq024\phases\sq024_05_the_big_race.questphase
contains all of the following focused shapes:
| Shape | Native type or reference |
|---|---|
| Start and stop the race subsystem | questStartRace_NodeType, questStopRace_NodeType |
| Return/recovery route | questGoBackToRace_NodeType plus player-facing restriction/objective state |
| Competitor movement | questvehicleRacingParams on vehicle command nodes |
| Course ownership | spline NodeRefs, including a race-specific spline in that phase |
| Opponent behavior | racing parameters, target refs, precision, push-aside, and rubber-banding policy |
| Player/vehicle state | mount and spawned conditions plus camera and gameplay restrictions |
| Progress and outcome | checkpoint resources/facts, VehicleRaceQuestEvent, Journal state, and finish/stop logic |
That phase also coordinates scenes, communities, race crowds, competitors, player-vehicle selection, and recovery. A row in the table is not sufficient on its own.
A safe authoring decomposition is:
course/world assets
-> competitor and player-vehicle readiness
-> pre-race scene and restrictions
-> start-race operation
-> checkpoint/progress subsystem + opponent movement
-> finish, leave-course, damage, and interruption branches
-> stop-race operation
-> restore restrictions, settle outcome, clean every dependency
For a new race, first document the course spline and checkpoint owner, then one competitor’s complete lifecycle, then the player recovery route. Do not claim a generic recipe until that reduced arrangement survives leaving the course, reloading mid-race, losing or destroying a competitor, finishing, and replay isolation.
Manual WolvenKit composition order
- Choose one bounded activity: stopped delivery, owner-specific cleanup, chase research, or race research. Do not start by combining all four.
- Extract only the relevant current-game comparison phase named above and record each inspected node’s containing graph, identities, decisive fields, and surrounding lifecycle.
- Author the mod-owned vehicle, occupants, trigger/spline/checkpoint assets, community/world ownership, journal entries, and unique NodeRefs before the quest child.
- For arrival-only delivery, bind the destination wait to the exact vehicle as a non-player activator. For stopped-inside delivery, require current trigger and speed state in one Boolean predicate or a deliberate recheck loop; do not use two latched historical signals as simultaneous truth.
- For cleanup, identify whether the target belongs to a community, world placement, player-vehicle record, or scene. Use only an operation whose identity domain matches that owner.
- For a chase, prove both vehicles and occupants spawned before seat assignment or AI; give success, lost-target, failure, stop, and cleanup routes explicit outputs.
- For a race, reduce the cited subsystem to one course and competitor while preserving start/stop, checkpoint, recovery, restriction, and cleanup ownership. Keep the result Experimental.
- Reopen every mod-owned phase and inspect handles, sockets, vehicle refs,
entry names, trigger refs, comparisons, facts, and terminal outputs.
Round-trip the resources with WolvenKit
8.19.0. - Pack and install one build. Run delivery/cleanup or chase/race cases from separate copies of an untouched pre-install save, retaining fresh logs and state captures.
- Promote only the exact observed result. Keep stopped delivery, generic cleanup, reload/interruption, chase, and race Experimental unless their own retained matrices pass.
Failure patterns
| Symptom | First boundary to isolate |
|---|---|
| Delivery completes when V walks in | isPlayerActivator or an empty/wrong activatorRef |
| Correct vehicle enters but never completes | Vehicle identity differs between trigger and speed condition, or exact-zero semantics never become true |
| Contact/occupants vanish during route | Community activation, streaming coverage, or assignment before spawned readiness |
| Cleanup removes the wrong vehicle | Player-record operation used for a community/world vehicle, or broad cleanup shares one owner |
| Chase starts but cannot finish | Lost-target/success/stop monitors do not converge or one route never releases the parent |
| Race UI or restrictions remain | Stop/restore cleanup is reachable only from the normal finish branch |
| Reload duplicates opponents or checkpoints | Save-backed stage facts and activation guards do not distinguish resume from restart |
Use NodeRefs, streaming, and placement, Handles, sockets, and resource references, and Controlled isolation and evidence to reduce the failure before changing several systems at once.
Acceptance matrices
For a simultaneous stopped-inside delivery and its cleanup, retain at least:
| Case | Required result |
|---|---|
| Wrong vehicle enters destination | No progress |
| Correct vehicle passes through without stopping | Delivery remains incomplete |
| Correct vehicle enters, leaves, then stops outside | No progress; the sequential structural control should expose its false positive here |
| Correct vehicle stops inside | Delivery succeeds exactly once |
| Reload while inside but moving | The stopped wait resumes without replaying earlier one-shot work |
| Reload after delivery before cleanup | Handoff and cleanup resume intentionally |
| Stream away after cleanup | No unintended respawn or remaining marker |
| Clean-save replay and removal isolation | No old vehicle/device/community state is mistaken for the candidate |
For a chase or race, add route departure, catch-up/recovery, competitor death or destruction, mid-route reload, normal finish, failure, interruption, and post-completion replay. Record vehicle/occupant counts and every restored restriction, not only the final quest fact.
Rewards, switches, and outcomes
An outcome, its debrief, its payout, journal success, and phase termination are independent effects. Compose them in an explicit order and preserve the outcome before presentation begins.
Tested with Cyberpunk 2077 2.31a, WolvenKit 8.19.0, ArchiveXL 1.27.0,
RED4ext 1.30.0, and redscript 0.5.31. See Tested
versions.
Use Rewards and completion for the reward node’s full property shape and Branching, choices, and debriefs for two-way and n-way branch construction. This page covers their composition.
Resource prerequisites
An outcome-dependent ending can cross several resource systems:
| Resource | Required content |
|---|---|
| Questphase | Outcome conditions, branch-specific effects, convergence, reward manager, completion fact, journal success, terminating output |
| Journal | Debrief contact/thread/messages or a debrief objective, plus the quest and objective entries being completed |
| Onscreen localization | Every debrief message, reply, objective, and completion-facing string |
| Reward record | A valid QuestRewards.* TweakDB record authored and tested separately |
| Optional scene/audio | .scene, actor/context binding, subtitles, VO map, WEMs, lipsync, and named outputs for a voiced debrief |
| World/community/device state | Explicit cleanup or stable completed state before the ending claims completion |
The reward node references a TweakDB record; it does not define the payout. Do not copy a vanilla reward record or assume a correctly spelled ID exists. If the quest needs a custom record, use a separately documented TweakDB authoring path and record that tool’s exact tested version. Custom reward-record creation is outside this page and remains Experimental for this book’s pinned release.
Persist outcome before the debrief
Write the durable result at the event that owns it:
player choice / combat result / device result
-> set durable outcome
-> finish branch-owned world and journal effects
-> clean or stabilize active gameplay systems
-> start debrief
The debrief consumes state; it should not invent it. A phone choice’s visited flag, a scene caption, an NPC’s current hostility, or the presence of one message is fragile presentation/runtime state. A named fact gives later phases one explicit contract and survives a resource boundary.
When several facts represent exclusive outcomes, validate that exactly one is positive before the debrief. When an integer fact represents an enum, connect an explicit unknown-value fallback. Old saves and interrupted writes can otherwise route the wrong presentation or stall forever.
Recipe: grant one standalone reward
The native operation is deliberately small:
In
-> questRewardManagerNodeDefinition
-> questGiveReward_NodeType
-> rewards[] = QuestRewards.my_quest_completion
-> Out
| Property | Contract |
|---|---|
type | Populated handle containing questGiveReward_NodeType |
type.rewards[] | One or more string-backed reward TweakDBID values |
| sockets | In, Out, and the separate CutDestination |
| external dependency | Every named reward record already exists and has the intended contents |
Observed in vanilla: extract
base\open_world\street_stories\watson\kabuki\sts_wat_kab_05\phases\sts_wat_kab_05_openworld.questphase
from your own installation. In the retained focused reference, top-level phase
node 5 owns an embedded phaseGraph; local node 4 inside that graph is the
reward manager whose nested operation names
QuestRewards.sts_wat_kab_05_completion. This proves that resource shape, not
permission to reuse its record or either graph-local node ID.
A standalone payout still needs a one-shot owner:
reward_claimed == 0?
False -> already-paid continuation
True -> grant reward
-> set reward_claimed = 1
-> continuation
This ordering has an interruption window between grant and fact write. Writing the fact first creates the opposite window, in which re-entry can suppress an ungranted payout. There is no automatic transaction. The new arrangement is Experimental until saves on both sides of the boundary, completed-save reload, and repeated entry are tested.
Recipe: outcome-dependent text debrief
This ending uses common context, one outcome-specific opening, then a shared response group and completion chain:
debrief objective Active
-> 1-second realtime pacing delay
-> common message 1 Active
-> common message 2 Active
+-> wait outcome_destroy > 0 -> destroy message Active --+
`-> wait outcome_spoof > 0 -> spoof message Active --+
|
XOR convergence
|
response group Active
+-> wait reply A Succeeded -> reply A --+
`-> wait reply B Succeeded -> reply B --+
|
XOR convergence
|
final message Active
-> 1-second pacing delay
-> objective Succeeded
-> reward
-> set quest_completed = 1
-> quest Succeeded
-> Out1
| Stage | Native owner |
|---|---|
| Outcome selection | Two questPauseConditionNodeDefinition fact waits and a two-input questLogicalXorNodeDefinition |
| Message presentation | questJournalNodeDefinition → questJournalEntry_NodeType, entered through Active |
| Player response | One questJournalEntryState_ConditionType per individual choice, state: Succeeded |
| Objective/quest presentation | Separate questJournalQuestEntry_NodeType nodes and state sockets |
| Payout | questRewardManagerNodeDefinition → questGiveReward_NodeType |
| Durable completion | questSetVar_NodeType, exact value 1 |
The custom debrief is Experimental. Test both openings, both final responses, payout, completion, and cleanup, including a reload before payment.
The exact retained phase uses a one-second realtime delay after activating its final message. That is a presentation-pacing choice, not proof that the player read the message. If acknowledgement is a design requirement, replace that boundary with the visited-state recipe below and test the changed graph. Do not treat a pacing delay as player acknowledgement.
Recipe: require acknowledgement before payout
This completion route places a visited gate before its common ending:
wait final debrief message visited
-> grant QuestRewards.<quest>_completion
-> set <quest>_completed exactly to 1
-> enter quest journal node through Succeeded
-> Terminating output
| Step | Exact node family |
|---|---|
| 1 | questPauseConditionNodeDefinition → questJournalCondition → questJournalEntryVisited_ConditionType, final gameJournalPhoneMessage path, visited: 1 |
| 2 | questRewardManagerNodeDefinition → questGiveReward_NodeType.rewards[] |
| 3 | questFactsDBManagerNodeDefinition → questSetVar_NodeType, setExactValue: 1, value: 1 |
| 4 | questJournalNodeDefinition → questJournalQuestEntry_NodeType, entered through Succeeded |
| 5 | questOutputNodeDefinition, type: Terminating |
When combining outcome-specific openings with a final-message visited gate, test every response path and reload before and after payment.
Same reward versus branch-specific rewards
When every outcome earns the same payout, converge first and keep exactly one reward node:
case A effects --+
case B effects --+-> convergence -> common reward -> completion
case C effects --+
When outcomes earn different payouts, keep the payout inside each branch and converge afterward:
Case101 -> reward A -> set payout_a_done = 1 --+
Case202 -> reward B -> set payout_b_done = 1 --+-> common journal success
Otherwise -> no-payout/fallback result --------+
Each case must still persist its narrative outcome before reward delivery. Keep payout facts separate if interrupted saves must distinguish “outcome was selected” from “reward was granted.” Never connect several case outputs to duplicated copies of an identical common reward merely to simplify layout.
The native reward node is Observed in vanilla, and a three-case switch is
Observed in vanilla in
base\open_world\minor_activities\watson\northside\ma_wat_nid_15\ma_wat_nid_15_phase.questphase.
The branch-specific reward composition above has no retained custom runtime
campaign and remains Experimental.
Text debrief versus voiced debrief
Use a journal phone conversation when the ending is text. Use a .scene when
the debrief is performed dialogue. A voiced phone call also needs explicit
phone start/end state; see
Messages, calls, and conversations.
For a scene debrief, replace the final-message gate with the exact named scene result that means the dialogue completed normally:
questSceneNodeDefinition named normal result
-> reward
-> completion fact
-> journal success
-> terminate
Do not pay on scene start, Holocall INT, a nonterminal section output, or an
interruption route unless that is the authored outcome. Map rejection,
interruption, and reload to explicit non-paying or recovery paths. The book’s
Lab 5 has one Structurally validated named scene handoff, but it has no
reward and does not prove this debrief composition. A new voiced payout route
is Experimental.
Cleanup before declaring completion
Completion presentation should match the remaining runtime state. Before the debrief or final success, decide who owns:
- community deactivation and surviving actor cleanup;
- device disablement or completed persistent state;
- active mappin and objective removal;
- scene normal/interrupted cleanup;
- temporary inventory removal;
- still-running waits, monitors, and losing choice routes.
Before completing the quest, clear or transfer ownership of every active objective, marker, actor, and interaction. Quest success does not clean up those systems automatically.
Manual authoring order
- Write the outcome table: source event, durable fact/value, debrief content, payout, cleanup, and fallback.
- Author and register all journal/localization content, then verify its full typed paths.
- Validate each
QuestRewards.*record independently from the quest graph. - Add outcome conditions or a switch and persist the selected result before presentation.
- Wire branch-specific debrief messages/scenes and side effects.
- Converge at the latest common point; put a shared reward after convergence or distinct rewards before it.
- Add the required final gate: visited message, named normal scene output, or another explicitly player-facing completion signal.
- Connect reward, completion fact, objective/quest success, cleanup, and termination as distinct nodes in the intended order.
- Reopen the resource and inspect every handle, socket, journal path, condition, fact, and reward ID.
- Round-trip the mod-owned phase with WolvenKit
8.19.0, then run every outcome from separate copies of one untouched save.
Acceptance matrix
| Case | Required evidence |
|---|---|
| Every narrative outcome | Correct debrief content and no content from another branch |
| Every final response | Correct reply, one continuation, intended acknowledgement behavior |
| No/unknown outcome | Explicit fallback; no indefinite hidden wait |
| Before debrief save | Correct outcome presentation after reload |
| After branch message/scene start | No wrong branch, duplicate content, or lost phone/scene state |
| Before reward | Reload pays exactly once when completion continues |
| Immediately after reward | Reload does not duplicate payout or suppress required completion state |
| Completed save | No debrief, reward, objective, marker, actor, device action, or root route repeats |
| Clean replay | Same archive reproduces first-run behavior from the original untouched save |
Capture the exact archive, loose ArchiveXL files, TweakDB inputs, framework versions, save IDs, before/after money-XP-inventory state, relevant facts, journal states, and fresh logs. A fact-only reset cannot clear phone, scene, journal, community, or device persistence.
Failure routing
| Symptom | Inspect first |
|---|---|
| Correct outcome but wrong debrief | Durable outcome write, mutually exclusive values, switch/case mapping |
| Both outcome messages appear | Losing wait, impossible dual facts, stale save, convergence wiring |
| Final message appears but completion stalls | Visited path/class/index, named scene output, or deliberate pacing gate |
| Reward node runs with no payout | Reward record existence/content, exact TweakDBID, framework logs |
| Reward repeats | Multiple completion routes, interruption window, missing payout/completion guard |
| Quest succeeds before dialogue finishes | Activation or pacing signal was mistaken for acknowledgement/normal scene completion |
| Quest completes with actors or marker present | Cleanup owner is absent or ordered after termination |
| One old save routes differently | Saved outcome, journal, scene, community, or device state; return to the untouched baseline |
Braindance and specialized scenes
Braindances coordinate rewindable scenes, recorded animation, layered clues, and cleanup. Custom braindance playback remains Experimental.
The native feature is not one resource or one scene-node option. A reviewable candidate crosses all of these owners:
questphase entry, player hold, and exit cleanup
-> rewindable .scene and its entry/exit contract
-> .scenerid actor, facial, cyberware, and camera channels
-> world scene markers, camera entity, clue entities, and support props
-> visual/audio/thermal clue events and discovery facts
-> journal progress, interruption cleanup, and replay policy
Breaking any join can still leave the neighboring files structurally valid.
For example, a .scenerid can serialize while its scene event points at the
wrong resource/serial pair, and a clue can appear on a timeline while its
world entity or discovery handoff is absent.
Reading route
Read these pages in order:
- Ownership and resource chain — which resource owns quest flow, rewindable screenplay, recorded animation, support entities, markers, clue state, and cleanup.
- RID playback and rewind — how RID resource/serial identities meet scene event references, and why body, facial, cyberware, camera, duration, and rewindability form one contract.
- Clue layers, cleanup, and acceptance — visual/audio/thermal clue ownership, normal and interrupted exits, save-backed state, replay policy, and a practical eight-case test campaign.
Read Scene resource anatomy, Screenplay sections and events, Entry, exit, and quest handoff, and Persistent state first. Braindance adds contracts to ordinary scenes; it does not replace them.
Vanilla references
Extract these resources from your own installed archives into a disposable inspection project. Do not publish or package the extracted files.
| Depot path | Focused question |
|---|---|
base\quest\side_quests\sq012\scenes\sq012_02a_braindance.scene | How one current vanilla scene joins a rewindable section, three RID handlers, support props, clue layers, interruption, and named entry/exit points. |
base\animations\quest\side_quests\sq012\sq012_braindance\rid\sq012_braindance__part_a.scenerid | How a version-5 scnRidResource owns actor and camera tags plus body, facial, cyberware, and camera animation buffers. |
base\quest\side_quests\sq012\entities\sq012_02a_braindance_bdview.ent | The visual-layer support entity selected by a scene prop record. |
base\quest\side_quests\sq012\entities\sq012_02a_braindance_bdfog.ent | The fog support entity selected by a scene prop record. |
base\quest\side_quests\sq012\entities\sq012_02a_braindance_bdsetup.ent | The render-to-texture setup entity selected by a scene prop record. |
base\quest\main_quests\prologue\q004\scenes\q004_05_bd_yorinobu.scene | A much larger main-quest comparison with one rewindable section, layered clues, RID events, quest nodes, and BD action management. |
base\quest\main_quests\prologue\q004\phases\q004_braindance.questphase | The surrounding quest-side setup and teardown decomposition; it is not a self-contained scene template. |
The first two resources were re-extracted from the local 2.31a archives and
serialized with WolvenKit 8.19.0 on 2026-08-09. The focused sq012 scene
contains three scnRidResourceHandler entries, one
scnRewindableSectionNode, 24 scnPlayRidAnimEvent events, three RID-camera
events, six clue events, and two braindance-visibility events. Those counts are
Observed in vanilla for that exact resource only, not minimums or a
copy-ready design.
What you will learn
You will learn to:
- name every owner in the resource chain and the typed identity used at each join;
- inspect a current vanilla reference without redistributing it;
- distinguish serialization success from RID/event compatibility and runtime behavior;
- design a mod-owned candidate and its cleanup contract without relying on a compatible authoring tools;
- run the eight test cases that cover forward playback, rewind, clue layers, normal cleanup, interrupted cleanup, and replay.
Creating compressed animation buffers requires a compatible animation production tool. Inspect the RID/event relationships before producing new assets, then test playback, seek, rewind, and cleanup in game.
Next: Ownership and resource chain.
Ownership and resource chain
A braindance review scene is a coordinated set of native resources. Treating
the .scene or .scenerid as the whole feature hides the world placement,
quest lifecycle, clue-state, and cleanup owners that make playback usable.
| Record | Value |
|---|---|
| Research review | 2026-08-09 |
| Game archives inspected | Cyberpunk 2077 Windows GOG 2.31a |
| Inspection and structural round trip | WolvenKit 8.19.0 |
| Practical framework baseline | ArchiveXL 1.27.0, RED4ext 1.30.0, redscript 0.5.31 |
| Runtime status | Experimental — no retained eight-case custom-candidate pass |
Research note: the named vanilla shapes on this page are Observed in vanilla. The mod-owned
.questphase/.scene/.sceneridresearch chain is Structurally validated because each binary serialized and deserialized with WolvenKit8.19.0. Its in-game playback, seek, rewind, layers, cleanup, interruption, and replay remain Experimental.
Start with owners, not filenames
registered root questphase
-> child questphase owns entry preparation and scene invocation
-> questSceneNodeDefinition resolves a mod-owned .scene
-> scnSceneResource owns screenplay, events, exits, and RID refs
-> scnRidResource owns recorded actor/camera animation data
-> scene props resolve records and support .ent resources
-> scene markers and clue references resolve placed world nodes
-> named scene outcome returns to the questphase
-> questphase restores player/world/UI state and commits durable outcome
No arrow can be replaced by a same-looking identifier from another domain.
A scene event ID is not a RID serial number, a scene performer ID is not a
community entry name, and a NodeRef is not a depot path.
Native ownership table
| Owner | What it owns | What it does not prove |
|---|---|---|
| ArchiveXL root registration | The one root questphase visible to the quest loader | That external child phases, scenes, RID files, or world nodes resolve |
questQuestPhaseResource | Entry flow, preparation, scene invocation, exit branch, post-scene cleanup, journal/fact handoff | Rewindable timeline semantics or recorded animation buffers |
questSceneNodeDefinition | Soft scene depot path, scene input, world marker, and named output sockets | That the scene can acquire all actors/props or play its events |
scnSceneResource | Actors, player actors, props, workspots, screenplay store, scene graph, entry/exit points, interruption scenarios, RID handlers, and resource-reference tables | The bytes for recorded actor/camera motion or world placement |
scnRidResource in .scenerid | Actor and camera tags, serial numbers, body/facial/cyberware channels, camera channels, compressed buffers, duration, joint/track cardinality | Scene performer assignment, clue logic, entry/exit, or cleanup |
scnPropDef plus TweakDB record | How a scene acquires/spawns a support prop such as BD view, fog, setup, or camera | The world marker at which it appears |
| Streamed world resources | Scene origin, support-prop marker, camera entity, clue entities, player hold/return anchors, and their transforms | Scene or quest execution |
scneventsClueEvent and scene quest nodes | Layer, active time interval, clue entity, discovery operation, and fact handoff | Journal state unless the quest consumes the result |
| Save | Facts, journal progress, active quest/scene state, and other persistent side effects | A clean replay of a rebuilt candidate |
This is why cleanup belongs to the questphase even when the last visible event is inside the scene. Scene completion is a signal; it does not automatically restore every state changed before playback.
The six typed joins
Record these joins explicitly in design notes and acceptance evidence.
1. Quest node to scene resource
The scene node’s soft resource reference is a depot path such as
mod\my_quest\scenes\my_bd.scene. Its configured scene input and world marker
must exist in that exact scene/world pair. Filename similarity cannot satisfy
either join.
The owning phase also declares the prefab root needed for its local NodeRefs. Follow Quest prefabs and NodeRefs and Entry, exit, and quest handoff.
2. Scene handler to RID resource
scnSceneResource.ridResources[] contains scnRidResourceHandler values. A
handler couples an scnRidResourceId with a .scenerid depot path. Later
scene-reference entries use that resource ID again; array position or a file’s
basename is not the durable join.
In the current sq012_02a_braindance.scene, three handlers address parts A,
B, and C. That is Observed in vanilla for this recording, not a requirement
that custom work use three files.
3. RID tag to recorded performer or camera
The .scenerid root is scnRidResource. Its actor and camera entries have an
scnRidTag containing a typed signature (CName) and an
scnRidSerialNumber. The inspected SQ012 part-A RID is version 5, contains
nine actor entries and one camera entry, and has separate body, facial, and in
one case cyberware channels. Those counts are Observed in vanilla only.
A scene’s resouresReferences collection maps animation references back to a
RID resourceId and animation serial number. Events then select an entry in
those scene-local reference arrays. All three levels must agree:
.scenerid tag / animation serial
<-> scene resourceId + serial reference
<-> event's scene-local animResRefId or animSRRefId
4. Scene performer to actor acquisition
RID animation events target an scnPerformerId; scene actors use
scnActorId, actor names, and acquisition plans. Community-backed performers
still require the readiness/acquisition lifecycle described in Activation,
readiness, and acquisition.
Recorded motion does not spawn a performer.
5. Scene prop to support entity and world marker
The inspected SQ012 scene defines bdview, bdfog, and bdsetup as
scnPropDef entries using entityAcquisitionPlan: spawnDespawn. Each entry
selects a Props.* TweakDB record and a global spawn-marker NodeRef. The
records ultimately select the three .ent resources cited by this section.
The focused entity roots are entEntityTemplate. The BD view and fog
resources contain mesh components; the BD setup resource contains a
render-to-texture camera component. A successful .ent round trip does not
prove that its record exists, its marker resolves, or the scene spawns it.
6. Clue event to world entity, discovery fact, and journal
An scneventsClueEvent owns a timeline interval and analysis layer. Its
gameEntityReference may address an actor within a community or a standalone
world NodeRef. Scene quest nodes can discover/toggle the clue and write a
fact. The owning questphase must deliberately consume that fact and update the
journal. See Clue layers, cleanup, and
acceptance.
Focused current-archive observation
Use the disposable-project procedure in Inspect a vanilla questphase, but search for these exact paths:
base\quest\side_quests\sq012\scenes\sq012_02a_braindance.scene
base\animations\quest\side_quests\sq012\sq012_braindance\rid\sq012_braindance__part_a.scenerid
base\quest\side_quests\sq012\entities\sq012_02a_braindance_bdview.ent
base\quest\side_quests\sq012\entities\sq012_02a_braindance_bdfog.ent
base\quest\side_quests\sq012\entities\sq012_02a_braindance_bdsetup.ent
Open the current .scene and inspect properties before its graph. Record only
focused notes:
| Property area | Current focused observation |
|---|---|
entryPoints / exitPoints | Two named entries and one named exit bind names to scnNodeId values. Names, not editor position, form the quest handoff. |
interruptionScenarios | One enabled distance-based scenario has separate interrupt and return thresholds. This is not proof of custom interruption cleanup. |
ridResources | Three handler entries each pair a typed resource ID with an exact .scenerid depot path. |
resouresReferences | RID body, facial, cyberware, and camera reference arrays sit beside ordinary gameplay/cinematic/lipsync sets. |
props | BD view, fog, setup, and camera are separate props rather than flags on the rewindable node. |
sceneGraph | One rewindable section owns timed RID, camera, clue, visibility, VFX, dialog, audio, attachment, and socket events. |
Do not copy the extracted resource into a mod project. It contains quest-local actors, records, NodeRefs, facts, scene IDs, and animation references. Retain the path, versions, a small property inventory, and—if useful—a local hash.
Author a mod-owned chain manually in WolvenKit
This is a planning and inspection order, not a claim that every field has a safe from-scratch editor default.
- Define the terminal quest outcome, replay policy, and cleanup obligations before creating animation.
- Allocate a mod-owned namespace for depot paths, facts, full NodeRefs, community entries, scene entry/exit names, actor/prop names, RID resource IDs, and serial numbers.
- Create or place the scene origin, support-prop marker, camera entity, clue entities, and player hold/return anchors in mod-owned world resources.
- Declare those world roots in the exact questphase that resolves them.
- Build an ordinary scene shell first: actors, props, entry, exit, interruption scenario, and a non-rewindable diagnostic route.
- Add a mod-owned
.scenerid; inventory every actor/camera tag, animation serial, channel, duration, joint count, and track count. - Add scene RID handlers and reference tables, then bind events by typed reference ID. Do not infer array indexes from another scene.
- Add the rewindable section, its timed events, layer conditions, clue events, and named outputs.
- Add quest-side preparation, scene invocation, normal cleanup, interrupted cleanup, journal/fact handoff, and replay guard/reset.
- Round-trip every CR2W with WolvenKit, pack only mod-owned resources, and execute the eight-case matrix from clean saves.
The scene editor may make some of these arrays easier to edit, but the native contract remains the same. If a required table or buffer cannot be authored safely in the current WolvenKit UI, stop at a focused inspection/structural prototype. Do not substitute a hidden generator and call that a reader-facing manual procedure.
Failure routing
| Symptom | Inspect first |
|---|---|
| Scene node never starts | Root registration, child resource path, input name, phase prefab root, scene origin, and actor readiness |
| Scene starts but recorded bodies do not move | RID handler path/ID, actor signature and serial, scene RID-reference row, event animResRefId, performer mapping, duration, and joint cardinality |
| Recorded camera is absent | Camera RID tag/serial, scene camera-reference row, camera event animSRRefId, camera NodeRef, camera entity, origin marker, and activation flag |
| View/fog/setup layer is missing | Scene prop record, corresponding .ent, support marker, prop spawn policy, visibility events, and stream availability |
| Clue appears but quest does not advance | Layer/time interval, clue entity reference, discovery node, fact name/value, and quest-side fact wait |
| Exit leaves V or UI in the wrong state | Named output route, normal/interrupted cleanup symmetry, teleport/restore ordering, UI/input restoration, and save history |
| Replay uses stale results | Discovery facts, journal state, one-shot guards, active scene state, and explicit reset/preserve policy |
Next: RID playback and rewind.
RID playback and rewind
The .scenerid stores recorded scene animation data. The .scene decides
which recorded channel plays, which performer or camera receives it, where it
is placed, and when it appears on a rewindable timeline. Neither resource is
self-sufficient.
Research note: the field shapes and focused counts from
sq012_braindance__part_a.sceneridandsq012_02a_braindance.sceneare Observed in vanilla on Cyberpunk 20772.31a, re-extracted and inspected with WolvenKit8.19.0on 2026-08-09. The retained mod-owned RID/scene pair round-trips with WolvenKit8.19.0and is Structurally validated. Custom playback, camera, seek, rewind, facial/cyberware channels, and interruption remain Experimental.
Test from an untouched pre-install save. Scene playback, playhead position, clue state, camera ownership, and quest facts can survive in saves or the current session. A save already inside the braindance is not a clean replay.
Two resources, three reference levels
The complete recorded-animation lookup is:
scnRidResource (.scenerid)
actor/camera scnRidTag(signature, serialNumber)
animation scnRidSerialNumber + buffer
|
| selected by resource ID + animation serial
v
scnSceneResource.resouresReferences
ridAnimations[] / ridFacialAnimSets[] / ridCyberwareAnimSets[]
ridCameraAnimations[]
|
| selected by scene-local reference-array ID
v
scnRewindableSectionNode event
performer + animResRefId
or cameraRef + animSRRefId
The misspelled serialized property name resouresReferences is the native
field exposed by these resources. Preserve the field name WolvenKit presents;
do not “correct” a serialized property based on prose.
RID resource identity
An scnRidResourceHandler in the scene binds:
- a typed
scnRidResourceId; and - an exact
.sceneridResourcePath.
The resource ID is scene data, not the depot-path hash and not an array index.
A scene reference such as scnRidAnimationSRRef then combines that resource
ID with an animation scnRidSerialNumber from the selected RID.
Scene-local reference identity
RID animation events do not carry the resource path and serial directly. A
body event’s scnRidAnimationSRRefId selects an entry in the scene’s RID
animation-reference arrays. A camera event’s
scnRidCameraAnimationSRRefId selects a camera-reference entry. Array
membership and typed ID must be re-audited whenever resources or channels are
added, removed, or reordered.
Performer and camera identity
scnPlayRidAnimEvent.performer is an scnPerformerId; it is not the RID
signature. The scene’s actor/player/prop definitions establish the performer
map. A camera event instead carries a world cameraRef NodeRef, a camera RID
reference, and an origin marker.
This produces two independent joins:
RID actor signature -> RID animation serial -> scene RID ref -> performer
RID camera signature -> RID camera serial -> scene camera ref -> camera NodeRef
Do not force actor and camera identities into one shared counter scheme.
What version-5 .scenerid owns
The focused SQ012 part-A resource has root type scnRidResource, version: 5,
and these principal fields:
| Field | Role |
|---|---|
nextSerialNumber | Next RID-local serial allocation; not a scene node/event ID |
actors[] | scnActorRid entries identified by scnRidTag |
cameras[] | scnCameraRid entries identified by scnRidTag |
Actor animations[] | Body/root-motion scnAnimationRid channels |
Actor facialAnimations[] | Facial channels with their own animation objects and cardinality |
Actor cyberwareAnimations[] | Optional cyberware channels; absence is a valid empty array for many actors |
Camera animations[] | Camera transform/optical channels referenced by RID-camera scene events |
In this exact vanilla part, nine actor entries and one camera entry are present. Seven actor entries have one body and one facial animation; one of those also has a cyberware animation. Two prop-like RID actors have no animation arrays. This observation demonstrates optional channel sets; it does not define a required custom layout.
Animation buffer invariants
An animation entry ultimately owns an animAnimation and an animation buffer
such as animAnimationBufferCompressed. Focused properties include:
- animation and buffer duration;
- frame count;
- joint count and track count;
- raw/constant key counts;
- extra joint/track counts;
- compressed or deferred buffer bytes;
- optional motion extraction;
- camera LOD samples for camera animation.
Those numbers must describe one compatible rig/channel layout. A binary can serialize while still containing the wrong joint order, wrong track mapping, invalid root-motion basis, mismatched duration, or incompatible facial/ cyberware cardinality. WolvenKit round-trip success is therefore necessary but not sufficient.
For every custom channel, record a small manifest like this before linking the scene:
| Manifest field | Record |
|---|---|
| RID depot path | Locates the recorded animation resource |
| RID root version | Prevents assumptions from another layout version |
| Actor/camera signature | Names the intended RID slot |
| Tag serial and animation serial | Proves RID-local identity |
| Channel | Body, facial, cyberware, or camera |
| Duration and frame rate basis | Supports event timing comparison |
| Joint/track counts and ordered rig contract | Detects compatible-looking but wrong buffers |
| Motion-extraction/root policy | Distinguishes trajectory motion from in-place pose |
| Source and conversion versions | Makes a later rebuild comparable |
Do not publish a vanilla .scenerid, its full serialized JSON, or its
compressed buffer bytes. The table is a focused provenance record, not a
redistribution mechanism.
Recorded placement is another contract
A body event observed in the SQ012 scene includes:
performer(scnPerformerId);animResRefId(scnRidAnimationSRRefId);animOriginMarkerwith a global scene-markerNodeRef;actorPlacement: SceneOrigin;startTimeandduration;- blend, collision, component, camera-parallax, and trajectory settings.
The first event in the inspected section uses the global origin
#sq012_02a_sm_braindance, begins at 0, and lasts 33333 scene-time units.
That exact value is Observed in vanilla, not a universal conversion rule
for seconds or frames.
If an actor’s recorded root is authored in a different coordinate basis than the scene marker, every otherwise valid pose can appear offset or rotated. Audit these as one transform equation:
world scene-marker transform
* event placement/origin policy
* RID actor root / motion extraction
= expected world-space performance
Test translation, yaw, elevation, and return-to-origin separately. A single
actor standing at (0, 0, 0) cannot expose every basis error.
Recorded-perspective camera
The inspected scneventsPlayRidCameraAnimEvent contains:
| Property | Focused meaning |
|---|---|
animSRRefId | Scene-local index into a RID-camera reference array |
cameraRef | Full world NodeRef for the camera entity |
animOriginMarker | Marker defining the recorded coordinate origin |
cameraPlacement | Placement policy such as SceneOrigin |
activateAsGameCamera | Whether the event takes over the game camera |
controlRenderToTextureState | Whether this event controls that state; not equivalent to the BD setup prop |
startTime / duration | Timeline interval that must agree with the recorded channel |
markCamerCut | Cut marking for this event |
The SQ012 scene’s camera NodeRef, the camera prop/entity, the RID camera
reference, the origin marker, and the event are five separate checks. Seeing a
camera in the streamed world does not prove the RID camera channel is active.
engine\scenesystem\camera.ent exists in the installed 2.31a archives and
is a useful type/shape comparison for a placed scene camera. Its existence is
not permission to reuse arbitrary vanilla placement or quest-local IDs.
Rewindable section anatomy
The native timeline container is scnRewindableSectionNode, not an ordinary
scnSectionNode with a rewind flag added elsewhere. In the focused SQ012
scene it owns:
sectionDuration.stu: 89300;ffStrategy: automatic;- forward slow/fast/very-fast modifiers
0.5,3, and6; - backward slow/fast/very-fast modifiers
0.5,3, and6; - 115 timed events;
- output sockets for ordinary progress and side routes.
Its event inventory includes dialog/audio, skeletal animation, RID actor animation, RID camera animation, clue windows, BD visibility, VFX, prop attachment, and socket events. This is Observed in vanilla for the named scene. It demonstrates that rewindability coordinates multiple event types; it does not require every custom recording to contain all of them.
Rewind safety questions
For each event, answer:
- Is it a pure function of current timeline time, or does it cause a durable side effect?
- What state must be reconstructed when the playhead seeks into its middle?
- What is undone when the playhead moves backward across its start?
- Does its end state differ when playback jumps past the whole interval?
- Does it target an actor, prop, camera, clue, VFX, UI state, fact, or socket?
- Is the side effect allowed to happen more than once?
Facts, journal mutations, item operations, spawning, and quest sockets deserve special scrutiny. A timeline may revisit their event time, while the quest side effect may be intentionally one-shot. Make that policy explicit instead of assuming rewind will undo persistent state.
Inspect the RID link in WolvenKit
Use a disposable project and extract:
base\quest\side_quests\sq012\scenes\sq012_02a_braindance.scene
base\animations\quest\side_quests\sq012\sq012_braindance\rid\sq012_braindance__part_a.scenerid
Then record this focused cross-reference without copying the resources:
- In the scene, list each
ridResources[]handler’s resource ID and depot path. - In
resouresReferences, list each RID animation/camera reference’s resource ID and serial number. - In the RID, find the corresponding actor/camera tag and animation serial.
- In the rewindable section, select one body event and one camera event; record their local reference IDs, performer/camera, origin, start, and duration.
- Verify that the event’s local reference resolves to the expected RID resource/serial pair.
- Compare the event interval with the animation duration and the section duration.
Do not rely on graph-screen position. The typed IDs and property values are the evidence.
Manual composition order for a mod-owned candidate
- Finish the ordinary scene actor/prop acquisition and named entry/exit contract before introducing RID playback.
- Add the mod-owned RID resource and inventory all tags, serials, channels, durations, joint counts, and track counts.
- Allocate a unique scene
scnRidResourceIdand add its handler/path. - Add scene-level body/facial/cyberware/camera references, pairing the exact resource ID with exact RID animation serials.
- Add one short body event and verify its performer, origin, placement, duration, and local reference ID structurally.
- Add one short camera event and verify its camera entity and marker chain.
- Only then add the full rewindable section and the remaining channels.
- Re-inspect every reference ID after array edits; do not assume earlier indexes stayed stable.
- Serialize to CR2W, serialize the binary back to JSON, and compare root type, RID handlers, serial mappings, event references, durations, and cardinalities.
- Keep the result Experimental until all runtime cases pass on the exact installed build.
This sequence localizes failures. It is not a claim that arbitrary compressed animation buffers can be authored correctly by filling WolvenKit fields by hand. A buffer-production tool may be needed, but it remains an author tool, not evidence that the native runtime contract passed.
Structural acceptance checklist
-
.sceneridroot type/version and each actor/camera tag are recorded. - Every tag and animation serial is unique in its required scope.
- Every buffer has expected duration, frame, joint, and track counts.
- Every scene RID handler resolves an exact mod-owned depot path.
- Every scene RID reference resolves an existing resource/serial pair.
- Every event’s local reference ID selects the intended scene reference.
- Performer/camera, origin marker, placement, start, and duration are explicit.
- The rewindable section contains no unexplained durable side effect.
- WolvenKit binary-to-JSON-to-binary conversion succeeds.
- The archive contains the intended depot paths before runtime testing.
Passing this checklist supports Structurally validated, not Runtime-proven.
Failure routing
| Symptom | Inspect first |
|---|---|
| Nothing moves but the scene advances | RID handler path/ID, scene reference resource/serial, event local ID, performer, and event interval |
| Wrong actor receives motion | RID signature/serial map, scene performer map, and event performer |
| Actor snaps or drifts | Scene marker transform, recorded root basis, motion extraction, placement policy, and duration/stretch |
| Body works but face or cyberware does not | Separate channel presence, joint/track cardinality, scene anim-set reference, actor component, and event binding |
| Camera remains gameplay camera | RID camera serial/reference, event local ID, cameraRef, activateAsGameCamera, marker, and camera entity |
| Forward playback works but seeking breaks | Event reconstruction at arbitrary time, side effects crossed by seek, interval coverage, and replayable state |
| Binary round-trips but crashes or corrupts at runtime | Buffer layout/cardinality, resource/serial mapping, actor rig compatibility, and installed build; serialization alone is not runtime proof |
Next: Clue layers, cleanup, and acceptance.
Clue layers, cleanup, and acceptance
Layered clues are not decorations on recorded animation. Each clue has a timeline window, an analysis layer, a target entity, discovery behavior, and a quest-state handoff. Normal exit, interruption, and replay must agree about which of those results persist.
| Record | Value |
|---|---|
| Research review | 2026-08-09 |
| Game / inspection | Cyberpunk 2077 Windows GOG 2.31a; WolvenKit 8.19.0 |
| Practical framework baseline | ArchiveXL 1.27.0; RED4ext 1.30.0; redscript 0.5.31 |
| Custom runtime evidence | Experimental — all eight required cases pending |
Research note: layered clue events, BD visibility events, scene-side discovery nodes, layer/perspective conditions, and support props are Observed in vanilla in the cited SQ012/Q004 resources. The retained mod-owned scene serializes these shapes and is Structurally validated. No custom package has a retained eight-case pass, so layer switching, discovery, cleanup, interruption, and replay remain Experimental.
The clue ownership chain
rewindable timeline interval
-> scneventsClueEvent(layer, clueName, entity, time range)
-> placed/acquired clue entity
-> BD layer/perspective conditions
-> questDiscoverBraindanceClue_NodeType or focused clue operation
-> dedicated discovery fact
-> questphase fact wait/join
-> journal objective and terminal outcome
Do not skip from “the highlight was visible” to “the quest recorded the clue.” Those observations sit at different points in the chain.
What a clue event owns
The focused SQ012 scneventsClueEvent entries expose these properties:
| Property | Responsibility |
|---|---|
clueName (CName) | Scene-local semantic name for the clue |
layer | Analysis layer such as Visual or Audio |
startTime / duration | Interval during which the clue belongs on the recording timeline |
clueEntity (gameEntityReference) | Target actor/community entry or standalone world NodeRef |
markedOnTimeline | Whether the event contributes a timeline marker |
factName / overrideFact | Optional event-owned fact behavior; do not assume every vanilla clue uses it |
type, executionTagFlags, scalingData | Additional event behavior that must be preserved/understood rather than dropped |
One inspected visual clue addresses actor name holt through community
#sq012_com_braindance; an audio clue addresses standalone NodeRef
#sq012_ent_rqr_audio_clue. These are Observed in vanilla examples of two
entity-reference shapes. They are not reusable identifiers.
The same SQ012 rewindable section contains six clue events: visual and audio
examples, each with its own interval. The larger Q004 Yorinobu scene contains
visual/audio/thermal analysis logic, 23 clue-toggle events, 27
questDiscoverBraindanceClue_NodeType nodes, and 112
scnBraindanceLayer_ConditionType values in the focused current extraction.
Those counts describe that exact main-quest scene; they are not minimums.
Layer state and discovery state are different
Keep at least four state questions separate:
- Which analysis layer is currently selected?
- Is the playhead inside this clue’s time interval?
- Is the clue entity available and focusable?
- Has the clue’s discovery result already been committed?
A Boolean design should usually require the applicable layer and interval before enabling focus/discovery. A discovery fact may intentionally remain true after the playhead rewinds or the player switches layers. That persistence must be an explicit quest design decision.
Use one dedicated fact per independently required clue. Joining three clue facts with a logical AND makes completion readable and testable:
visual_clue_found > 0 ---\
audio_clue_found > 0 ----- AND -> update objective -> enable finish/exit
thermal_clue_found > 0 ---/
This graph records discovery; it does not enforce simultaneous layer state. See Boolean trees and Signal flow.
Inspect and author clues in WolvenKit
Start in a disposable inspection project with
base\quest\side_quests\sq012\scenes\sq012_02a_braindance.scene and
base\quest\main_quests\prologue\q004\scenes\q004_05_bd_yorinobu.scene.
For one clue in each resource, record the event type, layer, clue name,
start/duration, entity-reference shape, timeline-marker policy, discovery
operation, fact, and downstream journal/finish route. Do not add either
vanilla scene to a distributable project.
For a mod-owned candidate, compose one layer at a time:
- Place or acquire one mod-owned clue entity and give it a fresh full
NodeRefor actor/community reference. - Add one
scneventsClueEventto the rewindable section with an interval entirely inside the section duration. - Set the exact analysis layer and decide whether the clue is marked on the timeline.
- Add the layer/interval/focus path and one discovery operation.
- Write one dedicated fact and wait for that exact fact in the owning questphase before changing journal state.
- Add the clue’s temporary focus/highlight state to both normal and interrupted cleanup ledgers.
- Serialize the
.sceneto CR2W, reopen or serialize it back to JSON, and verify that event identity, layer, interval, entity reference, and fact survived. - Repeat for audio and thermal only after the visual clue passes structural inspection; do not duplicate scene-local IDs or facts.
WolvenKit round trips and a visible property graph support Structurally validated. They do not prove that the runtime switches layer, discovers the entity, writes the fact, or reconstructs the clue after a seek.
Support props are lifecycle resources
BD view, fog, and setup are separate scene props in the inspected SQ012 scene.
Their scnPropDef entries use a Props.* TweakDBID, a spawn/despawn
acquisition plan, a unique dynamic entity name, and a global support-marker
NodeRef.
The corresponding focused vanilla entity resources are:
base\quest\side_quests\sq012\entities\sq012_02a_braindance_bdview.ent
base\quest\side_quests\sq012\entities\sq012_02a_braindance_bdfog.ent
base\quest\side_quests\sq012\entities\sq012_02a_braindance_bdsetup.ent
The view/fog entities are mesh-bearing entEntityTemplate resources. The
setup entity includes entRenderToTextureCameraComponent. Their scene
definitions, TweakDB records, entity paths, spawn marker, visibility events,
and cleanup policy must agree.
The camera is another prop/world entity and another RID binding. Do not use the BD setup prop as evidence that the recorded-perspective camera exists or that the gameplay camera was restored.
Separate temporary and durable state
Classify every mutation before building the exit routes.
| State | Typical owner | Normal-exit policy | Interrupted-exit policy | Replay policy |
|---|---|---|---|---|
| Selected layer / playback speed | BD scene/runtime UI | Reset | Reset | Start from documented default |
| Gameplay camera and render-to-texture state | Scene events/support entities | Restore | Restore | Reacquire from clean state |
| Input/UI/action masks | Quest/scene managers | Restore | Restore | Reapply only on entry |
| Player hold/return placement | Questphase/world anchors | Return safely | Return safely | Re-enter through entry gate |
| Spawned view/fog/setup/camera props | Scene acquisition | Despawn/release | Despawn/release | Fresh acquisition |
| Temporary clue focus/highlight | Scene/clue system | Clear | Clear | Reconstruct from playhead/layer |
| Discovery fact | Facts database | Preserve or reset by explicit design | Same explicit policy | Must match documented replay design |
| Journal objective state | Journal resource/save | Commit only at defined handoff | Do not accidentally commit | Resume or remain complete by explicit design |
| Active scene/phase state | Scene and questphase/save | Reach named exit/terminal output | Reach interruption cleanup/output | Must not retain a stuck active owner |
If normal and interrupted cleanup disagree on temporary state, the second run is not a valid replay test.
Normal exit contract
A conservative quest-side route is:
prepare world and acquire performers
-> place/hold player and set required UI/input state
-> start rewindable scene at named input and origin
-> receive named normal outcome
-> restore camera/render/UI/input state
-> return player to safe anchor
-> release support props and transient owners
-> commit discovery/journal outcome once
-> complete child phase
The exact manager nodes depend on the candidate. Explain every changed state and its inverse. Delay only when the target system has an asynchronous lifecycle that requires it; a magic sleep is not a cleanup proof.
Interrupted exit contract
Interruption may arrive while an actor, camera, clue, or support effect is active. It therefore needs a first-class route, not a connection directly to the normal terminal node:
interruption / CutDestination / abort signal
-> stop or cut scene ownership
-> disable discovery interaction
-> restore camera/render/UI/input state
-> return player if relocation occurred
-> release props, actors, and transient world operations
-> preserve or roll back durable facts according to policy
-> leave through a named, testable phase output
The inspected SQ012 scene includes one enabled distance-based
scnInterruptionScenario with different interrupt and return thresholds.
That is Observed in vanilla scene structure. It does not prove that an
arbitrary custom questphase’s CutDestination or cleanup path is correct.
See Complex cleanup, interruption, and cancellation for the cross-system ownership ledger.
Save and replay boundary
Facts, journal state, quest checkpoints, active scene state, spawned communities, and some device/world state can survive archive replacement. Use the specified starting save and keep the installed build unchanged.
Prepare at least these save seeds:
seed-before-install: created before any version of the candidate was installed;seed-before-entry: candidate installed, quest available, BD child not yet active;seed-in-playback-early: optional diagnostic only; never substitute it for the clean normal-entry case;seed-after-normal-cleanup: produced by the exact package under test and used only for the replay case.
Do not load a mid-scene save captured from an older archive to test a rebuilt RID or event table. The save may retain an active scene/checkpoint layout that no longer matches the package.
Mandatory eight-case runtime matrix
Run each case separately; seek, rewind, layer discovery, and cleanup can fail independently.
| Case | Start and action | Required observations | Failure boundary |
|---|---|---|---|
| Case 1 — forward seek | Clean seed-before-entry; enter normally, then seek from an early time to a later time crossing actor, camera, and clue intervals | Correct reconstructed actor/camera/prop state at destination time; no duplicated durable side effect; controls remain responsive | Ordinary linear playback is not a forward-seek pass |
| Case 2 — backward rewind | Separate clean clone; play to a later interval, rewind across camera/body/clue boundaries, then resume | Earlier pose/camera/visibility/focus state is reconstructed; no stuck later effect; resume remains coherent | Moving the playhead without reconstructing state fails |
| Case 3 — visual layer | Separate clean clone; select visual layer during a visual clue interval and discover it | Only intended visual clue is available; timeline/entity/focus agree; exact visual fact changes once; journal does only the documented work | Seeing a highlight without fact/journal proof fails |
| Case 4 — audio layer | Separate clean clone; select audio layer during an audio clue interval and discover it | Intended audio clue and playback/focus behavior appear; exact audio fact changes once; other facts remain unchanged | Hearing scene audio is not an audio-clue pass |
| Case 5 — thermal layer | Separate clean clone; select thermal layer during a thermal clue interval and discover it | Intended thermal clue is available only under the documented conditions; exact thermal fact changes once | A visual/VFX approximation is not a thermal-layer pass |
| Case 6 — normal cleanup | Separate clean clone; discover required clues and leave by the normal scene outcome | Camera, render state, UI/input, player placement, performers, support props, highlights, quest child, facts, and journal match the ownership ledger; save/reload remains stable | Reaching the next objective while leaking temporary state fails |
| Case 7 — interrupted cleanup | Separate clean clone; interrupt at the most stateful documented point (active RID camera plus clue/support effects) | Cut route runs once; player/camera/UI/input/world owners are safe; durable-state policy is honored; reload is stable | Testing interruption before playback acquires anything is insufficient |
| Case 8 — replay after cleanup | Load seed-after-normal-cleanup made by this exact candidate; invoke the documented replay/resume route | Replay eligibility matches policy; scene starts from a coherent state; preserved discoveries and reset temporary state behave exactly as documented; second cleanup succeeds | Restarting from a pre-entry clean save is not replay-after-cleanup |
For every row, retain:
- case ID and pass/fail;
- installed archive and external asset paths;
- game, WolvenKit, ArchiveXL, RED4ext, redscript, and relevant audio/animation tool versions;
- starting-save identity and provenance;
- expected and observed facts/journal states before and after;
- expected and observed actor, prop, camera, UI/input, player-placement, and scene/phase ownership;
- timestamps or screenshots/log excerpts sufficient to audit the observation;
- unexpected behavior and whether the case was rerun from a fresh clone.
Check that all eight cases passed for the exact package you intend to distribute. Repeat the affected tests after changing its resources.
Failure routing
| Symptom | Inspect first |
|---|---|
| Correct layer, no focusable clue | Event interval, layer, clue entity reference, entity streaming/acquisition, and focus toggle |
| Clue highlights but fact remains zero | Discovery node/event path, exact fact name, overrideFact, signal ordering, and save history |
| Fact changes but objective does not | Questphase fact condition, logical join, journal path/state, and one-shot guard |
| Rewind leaves highlight/VFX behind | Event interval reconstruction, layer condition, clue/VFX end state, and side effects crossed by reverse playback |
| Normal exit leaves BD presentation active | View/fog/setup prop cleanup, camera/render restoration, UI/input masks, and named outcome route |
| Interrupted exit strands the player | Cut route, scene ownership termination, player return anchor, and cleanup ordering |
| Replay skips or instantly completes | Preserved discovery facts, journal state, completion fact, active scene state, and explicit replay gate/reset |
| Old and new builds behave differently on the same save | The save is contaminated evidence; return to a documented clean seed before changing resources |
This is the final braindance page. Return to Braindance and specialized scenes or continue to Troubleshooting.
Troubleshooting
A useful quest diagnosis starts by locating the first boundary that failed. Do not edit a scene because a journal entry is missing, or rebuild a graph because an old save already contains its earlier state.
Use this section after the focused authoring chapter for the resource in question. Each guide follows the same route:
symptom
-> earliest failing boundary
-> smallest decisive test
-> one controlled correction
-> structural verification
-> clean-save runtime retest
Tool versions
The procedures target the book’s pinned version set:
| Component | Version |
|---|---|
| Cyberpunk 2077 | Windows GOG 2.31a |
| WolvenKit | 8.19.0 |
| ArchiveXL | 1.27.0 |
| RED4ext | 1.30.0 |
| redscript | 0.5.31 |
Use the tested versions when reproducing a problem, and record any differences in your setup.
Route by symptom
| Symptom | Start here | First question |
|---|---|---|
| A file saves or packs, but content is absent or the game crashes | Serialization versus runtime | Did the cooked binary retain the authored topology and scalar values? |
| The root never starts, or a child/resource reports missing | Registration and depot paths | Is the exact resource both packed and reached by the correct registration/reference edge? |
| An edge disappears, a node points at the wrong object, or conversion fails | Handles, sockets, and resources | Is this an internal handle, graph socket, or external resource reference? |
| A trigger, marker, actor, or device is unresolved or location-dependent | NodeRefs, streaming, and placement | Does every local/full NodeRef join reach a loaded concrete world node? |
| A scene crashes at launch, an actor is missing, or dialogue never starts | Actors, scenes, and lipsync | Was the actor ready, acquired by the same identity, and assigned an addressable lipsync slot? |
| A quest has no title, a subtitle is missing, audio is silent, or a choice label is stale | Journal, localization, and audio | Which of the separate journal/UI, spoken-line, or embedded-choice lookup paths failed? |
| Only old saves fail, a completed quest repeats, or a device keeps stale state | Save state and clean retests | What state did the save already contain before these bytes were installed? |
| Several changes landed together and causality is unclear | Controlled isolation and evidence | What one variable differs from the last unchanged baseline? |
Find the earliest failing layer
Check layers in order. A later symptom can be the consequence of an earlier failure.
| Layer | Decisive evidence | If it fails |
|---|---|---|
| Authored resource | Exact node/resource/property inventory | Correct the WolvenKit project before cooking |
| CR2W round trip | Serialized output retains semantic topology and values | Repair the resource; do not continue to packing |
| Archive payload | Archive listing and extracted payload hashes | Correct project paths or stale build output |
| Loose framework files | Installed .archive.xl and dependency inventory | Correct staging before opening a save |
| Registration | ArchiveXL/RED4ext/redscript logs | Correct the exact path or registration class |
| Native lookup | Journal path, ResourcePath, NodeRef, actor, or localization join | Test the relevant ownership chain |
| Runtime lifecycle | Player-facing oracle plus logs | Test readiness, ordering, cleanup, and interruption |
| Save restoration | Named starting save and reload observations | Return to a valid seed; do not infer from a contaminated save |
An archive existing in archive\pc\mod proves only that a file was copied
there. A clean ArchiveXL log proves only that the framework processed its
configuration. Neither proves the quest graph ran.
Preserve the failing state before editing
Before changing anything:
- Close the game and framework processes.
- Record the installed mod and framework inventory.
- Hash the
.archive,.archive.xl, and every other candidate-owned loose file. - Preserve the full relevant logs and hash them before the next launch overwrites or appends to them.
- Record the starting save slot and whether it predates the first candidate.
- Write the expected player-facing result and the exact observed deviation.
- Preserve a privacy-reviewed focused excerpt, screenshot, video, or note.
That snapshot turns “it broke” into a reproducible candidate. The final guide explains how to reduce it without changing several boundaries at once.
Next: Serialization versus runtime.
Serialization versus runtime validity
A successful save, conversion, or archive build is an intermediate result. The game can receive a readable CR2W whose graph is smaller than the authored graph, whose nested values are stale, or whose external references cannot resolve.
Recognize the misleading success
| Symptom | Likely boundary | Decisive test |
|---|---|---|
| Converter exits zero but nodes are missing | CR2W object-graph/topology write | Serialize the produced binary back and compare semantic node counts and edges |
| Graph shape matches but a path is still a template token | Nested scalar write | Search the round trip for unexpected placeholders and compare decisive properties |
| Archive packs but root never starts | Registration or depot path | List/extract the archive, then inspect ArchiveXL registration logs |
| ArchiveXL registers everything but load crashes | Native resource content or runtime lookup | Reduce to the earliest activation boundary; inspect scene/world/resource cardinalities |
| Behavior differs after reinstalling identical bytes | Save-backed state | Compare a pre-install original with a completed or contaminated save |
Do not diagnose from the last visible symptom alone. A journal objective that never appears can be caused by an omitted node, a stale journal path, failed registration, or a saved branch that already passed the activation node.
Compare meaning, not bytes
An intentional CR2W edit can legitimately change handle allocation, import order, or other serialization details. A useful round-trip comparison freezes the semantic contract instead:
- root type and version;
- graph node types and graph-local IDs;
- input/output/cut socket names and roles;
- every source-socket to destination-socket edge;
- decisive nested conditions, operations, paths, names, and numeric IDs;
- resource-reference array lengths and every referenced index;
- world registries, NodeRefs, placements, and typed buffer rows;
- journal paths and
fileEntryIndexvalues; - absence of unrequested placeholder strings.
For a nine-node authored graph, a readable six-node round trip is a failed write. It must not be packaged merely because both files begin with a CR2W header.
Run the boundary test
- Save the resource in WolvenKit and close its editor tab so the binary on disk is final.
- Convert or serialize that binary to an isolated review directory.
- Compare it with the authored inventory. Start with counts, type sets, sockets, edges, and decisive values rather than a full textual diff.
- Reopen the binary in WolvenKit and inspect the same nodes and nested properties.
- Pack only after the focused comparison passes.
- List and extract the candidate archive to a new directory.
- Hash each extracted payload against the project output and record the candidate archive hash.
- Install that exact candidate and confirm the installed hash still matches.
If a tool cannot represent an edit, the safe result is a clear failure. Do not keep an output that silently retained a template’s smaller array or older scalar.
Narrow the fix
When the round trip is wrong:
- Return to the last structurally correct resource.
- Recreate only the first missing object or value in WolvenKit.
- Save and round-trip immediately.
- Verify all existing topology as well as the new edit.
- Delete the rejected candidate from install staging so it cannot be mounted accidentally.
When the round trip is correct but runtime still fails, stop editing the serializer boundary. Move to registration, references, lifecycle ordering, or save state according to the earliest observed failure.
Claims after a correction
| Result | Supported label |
|---|---|
| Binary round trip and semantic checks pass | Structurally validated |
| Same shape exists in a cited vanilla resource | Observed in vanilla |
| Exact unchanged candidate passes the defined game route | Runtime-proven |
| Candidate is unplayed, reload cases are incomplete, or several variables changed | Experimental for those runtime claims |
A non-crashing load alone does not prove journal presentation, cleanup, reload, or replay. Promote only the cases actually exercised.
Previous: Troubleshooting. Next: Registration and depot paths.
Registration and depot paths
Registration makes a root resource reachable. Packing makes a resource available at a depot path. A working quest usually needs both, but not every resource is registered in the same way.
Separate the four edges
archive payload path
|
+-> loose ArchiveXL configuration
|
+-> registered root
|
+-> native child/resource references
| Edge | Example owner | Failure |
|---|---|---|
| Packed payload | .archive depot tree | Resource does not exist at the path requested |
| Loose configuration | .archive.xl beside the archive | ArchiveXL never sees the registration instructions |
| Root attachment | quest.phases, journal, localization, or streaming.blocks | Root system does not expose the mod resource |
| Nested resolution | phaseResource, subtitle map, block descriptor, or another CR2W reference | Root registers, but a child dependency fails later |
An external child questphase is not automatically another root. Registering both parent and child can create two independent root attachments while still leaving the parent’s intended composition unproved.
Diagnose a quest that never starts
- Close the game and every framework process.
- Inventory
archive\pc\modfor every candidate-owned.archiveand.archive.xl. Hash every intended and duplicate file before changing the live directory. - Move or disable every unintended live copy into a named quarantine outside all active mod/framework paths. Do not delete the preserved evidence.
- Inventory
archive\pc\modagain and require exactly the intended candidate pair, with no renamed or older copy that can still mount. - Hash the intended pair and compare it with the built candidate.
- List the archive and confirm the exact root depot path appears once.
- Extract that entry and compare its hash with WolvenKit project output.
- Open the
.archive.xlas text and compare itspathcharacter for character with the archive listing. - Launch to the load menu and preserve fresh RED4ext, ArchiveXL, game, and redscript logs.
- Search for the candidate filename, resource path, and the first missing dependency. Record an error’s full context rather than one line.
- Only after registration is clean should you test the graph from a valid starting save.
Depot paths use backslashes in the canonical resources in this book. Do not
silently add a filesystem drive, project source\archive prefix, or game
install prefix to the virtual depot path.
Diagnose a root that registers but stalls later
Trace the first external reference reached by execution:
| Symptom | Inspect next |
|---|---|
| Parent enters a phase node and stops | phaseResource.DepotPath, soft reference form, child archive entry, and child In1 interface |
| Journal entries never render | Journal registration, full gameJournalPath.realPath, fileEntryIndex, and entry state socket |
| Text is blank | Correct localization registration class and exact secondary key or numeric RUID |
| World objective has no trigger/marker | Registered streaming block, descriptor sector path, prefab root, and child NodeRef chain |
| Scene starts but line has no text/audio | Subtitle-map and VO-map registrations plus their nested resource paths |
Do not add a second root registration to repair a missing nested path. Fix the edge that owns that path.
Compare registration classes
ArchiveXL configuration sections are not interchangeable:
| Responsibility | Registration surface used in the labs |
|---|---|
| Executable root questphase | quest.phases with mod path and vanilla parent |
| Journal tree | journal |
| Journal/UI text | localization.onscreens for the locale |
| Spoken subtitles | localization.subtitles for the locale |
| Spoken audio lookup | localization.vomaps for the locale |
| World loading | streaming.blocks |
A .scene, external child questphase, subtitle entries resource, VO WEM, or
streaming sector can be packed and reached through another registered resource
rather than registered as an independent root. Follow the ownership chain in
the relevant chapter.
Stale-install control
A duplicate archive can make a removed resource appear to work. A duplicate
.archive.xl can register an old path while the current file is wrong. For an
isolation run:
- keep one exact candidate pair;
- keep only the pinned framework stack and explicitly required lab files;
- record unrelated mod inventory as absent rather than relying on memory;
- restart the game after any mounted-file change;
- hash installed bytes again before interpreting the result.
Changing the installed pair does not clean save-backed facts, journal state, active nodes, scenes, communities, or devices. Use Save state and clean retests before calling a registration correction successful.
Common false fixes
| False fix | Why it misleads |
|---|---|
| Register every CR2W as a root | Changes ownership and may start resources independently |
| Copy an extracted vanilla parent into the archive | Creates a global override and redistributes game content |
| Rename only the project file | Leaves ArchiveXL and nested ResourcePaths pointing at the old depot path |
| Edit the graph until a log error disappears | Registration occurs before the affected runtime branch |
| Test only an old completed save | Its guard can bypass the entire broken route |
Previous: Serialization versus runtime. Next: Handles, sockets, and resources.
Handles, sockets, and resource references
Three kinds of connection often look alike in WolvenKit: an internal CR2W handle, a graph socket edge, and a path to another resource. They fail at different times and need different tests.
Identify the connection domain
| Connection | Typical form | Scope | Question |
|---|---|---|---|
| CR2W handle | HandleId / HandleRefId | One serialized CR2W object graph | Which object does this field own or reference? |
| Quest graph edge | Source socket to destination socket | One questphase graph | Which output causes execution to enter which input? |
| Scene graph stamp | Named/ordinal socket stamps and destinations | One scene graph | Which stamped route joins these nodes? |
| External resource | ResourcePath, often hard or soft | Virtual depot | Does the packed/registered resource exist at this exact path? |
| World identity | NodeRef | Streaming/world namespace | Is a loaded world resource registering this identity? |
A graph node ID is not its CR2W HandleId. A NodeRef is not either one. See
Identifier domains before editing a
number merely because it resembles another identifier.
Diagnose an internal handle failure
Symptoms include conversion errors, a missing nested object after round trip, an edge whose destination disappears, or a crash when a handled payload is first used.
- Serialize the binary back to an isolated review file.
- Inventory every
HandleIdand everyHandleRefIdin the affected CR2W. - Require each reference to resolve to exactly one compatible object.
- Check ownership: two fields may intentionally reference one object, but a copied node must not accidentally alias the old node’s sockets or payload.
- Inspect the target object’s RED type, not only the matching number.
- Reopen the binary in WolvenKit and verify the nested object is still owned by the intended field.
Let WolvenKit maintain handle identities through normal editor operations. When comparing round trips, compare the resulting object relationships rather than requiring the same incidental numbers.
Diagnose a socket failure
Start with the source and destination nodes and write the intended edge in one line:
node 12 / Out -> node 13 / Active
Then verify:
- the source socket is an output or the node type’s corresponding source role;
- the destination socket is an input with the intended operation semantics;
- spelling and case match the concrete socket contract;
- both socket handles still belong to their intended nodes after round trip;
- the source connection targets the destination socket, not merely the destination node;
- a parallel fan-out is deliberate and does not masquerade as an ordered sequence;
- a
CutDestinationroute is not connected to normal success by accident.
Journal nodes are a common example: entering Active, Succeeded, or
Inactive on the same node can produce three different state changes. A
visible edge is not correct merely because it reaches the right box.
For a child questphase, compare both sides of its public interface:
parent phase-node input In1 -> child questInputNodeDefinition In1
child questOutputNodeDefinition Out1 -> parent phase-node output Out1
The parent can resolve the child resource and still stall if the socket names or terminating output contract do not match.
Diagnose a resource-reference failure
An internal handle can be valid while the object it owns contains a missing external path. Trace the external edge separately:
- copy the exact
ResourcePathfrom WolvenKit; - identify whether the reference is hard, soft, or reached through a registered map/block;
- list the archive and confirm the target path exists;
- inspect any intermediary owner, such as a subtitle map’s entries path or a streaming block descriptor’s sector path;
- check ArchiveXL logs for registration/merge errors;
- confirm the target resource’s root type is compatible with the referring property.
Do not repair a missing ResourcePath by changing a HandleRefId; the handle
may correctly point at the soft-reference object whose path is wrong.
Address indexed resource tables
Some scene fields select a row in a resource table. The actor’s index and the table cardinality form one contract:
maximum referenced index < number of addressable cooked rows
Duplicate source rows do not guarantee duplicate runtime rows. If two entries
resolve to one cooked import while an actor requests index 1, conversion can
succeed and runtime can still perform an out-of-range lookup. Inspect the
round-tripped resource-reference array and every consumer index. The dedicated
Actors, scenes, and lipsync guide applies this to
lipsync slots.
Minimal repair loop
| Failure | Smallest safe correction |
|---|---|
| Dangling handle | Recreate the referenced nested object through WolvenKit and reselect it from the owning property |
| Wrong shared ownership | Duplicate through an editor operation that creates an independent object, then rewire its fields |
| Missing graph edge | Connect the exact named source and destination sockets |
| Wrong operation socket | Reconnect to the intended semantic input, such as Succeeded rather than Active |
| Missing external path | Correct the ResourcePath or pack/register its target; do not alter internal handles |
| Indexed table too short | Supply addressable distinct resources or reduce consumers only as an explicit diagnostic |
Round-trip after each correction. If several edges are repaired at once, a passing result cannot identify which defect caused the original symptom.
Previous: Registration and depot paths. Next: NodeRefs, streaming, and placement.
NodeRefs, streaming, and placement
A valid-looking NodeRef does not create a world object. The reference must join the questphase’s namespace to a loaded streaming block, the intended sector, a registered full child identity, and a concrete placed node.
Audit the complete identity chain
For a quest-prefab child, write all four values side by side:
quest local child: #example_trigger
phase prefab root: #example_prefab
block full root: $/mod/example/#example_prefab
sector full child: $/mod/example/#example_prefab/#example_trigger
Then find the concrete node and placement data associated with that full child
in the authored sector. In the compact tutorial sectors, NodeIndex connects
node data to nodes[]; do not assume row position is the relationship, and do
not generalize that compact layout to every cooked vanilla sector.
Route by symptom
| Symptom | First diagnosis | Decisive test |
|---|---|---|
| Trigger never fires anywhere | Broken identity chain, notifier, activator contract, or condition state | Trace local child, root, descriptor, full child, concrete trigger/notifier, and consuming condition |
| Object works only when already nearby | Streaming descriptor/bounds or lifecycle dependency | Start outside, approach normally, then stream away and return on a controlled save |
| Marker exists but quest cannot find it | Marker identity differs from quest NodeRef | Compare exact local/full paths and phase-prefab owner |
| Quest finds a trigger but it is in the wrong place | Placement transform or trigger geometry | Inspect position, rotation, scale, and shape points in the sector |
| Mappin log cannot resolve position | UI lookup cannot resolve the target identity | Use a proven marker strategy and keep gameplay/UI identities explicit |
| Device looks correct but old behavior persists | Save-backed controller identity | Retest from pre-install save; for deliberate identity invalidation, use a fresh NodeRef |
| Fast travel crashes while ordinary approach works | Streaming-time activation race or malformed streamed resource | Preserve crash boundary, test block/sector alone, then reintroduce quest activation |
Check loading before logic
- Confirm ArchiveXL registers the exact streaming block path.
- Confirm every descriptor’s sector path exists in the archive.
- Check the descriptor category and bounds. A finite Quest descriptor must cover the approach and intended activity; an AlwaysLoaded sector has a different lifetime and should not become a default dumping ground.
- Confirm
questPrefabNodeRefis present where the world-side root binding is required. - Confirm the sector registers each full child in
nodeRefs. - Inspect the associated concrete node and its node data.
- For a trigger, inspect its
questTriggerNotifier_Quest: require the intendedisEnabled,includeChannels, andexcludeChannelsvalues. - Inspect the consuming
questTriggerCondition: matchtriggerAreaRefand predicatetype, and verifyisPlayerActivatorplusactivatorRef. Labs 3–5 useisPlayerActivator: 1with the exact empty entity-reference payload; do not substitute an arbitrary actor NodeRef. - Test the world resource without starting the dependent scene or complex quest branch when possible.
If the sector does not load, editing the trigger condition cannot help. If the sector loads but the quest cannot resolve a child, compare the prefab namespace before moving geometry.
Check placement and trigger geometry
A NodeRef can resolve perfectly while its object is above, below, rotated away from, or too far from the player. Record:
- world position and orientation;
- sector node transform and any local transform;
- trigger point order, height, and scale;
- expected horizontal and vertical approach paths;
- descriptor bounds and streaming distance;
- whether the player was teleported, fast-travelled, or approached normally.
Test trigger state with one route at a time. An IsInside wait and an
IsOutside cleanup condition need different starting positions and save
points. Increasing every trigger radius after one missed activation can hide a
vertical placement error or make several scene conditions true in the same
startup tick.
Separate world and UI targets
A gameplay operation may need a native device NodeRef while the journal UI needs a marker whose position is reliably resolvable. Keep the responsibilities explicit:
| Responsibility | Possible owner |
|---|---|
| Device reservation/interaction | Native or mod-owned device NodeRef |
| Quest condition | Trigger or device NodeRef under the quest prefab |
| In-world scene placement | Scene marker NodeRef |
| Journal/map presentation | Dedicated static marker/mappin NodeRef |
Using separate identities is not automatically correct; it is a design that must preserve coordinates, lifecycle, and cleanup. Conversely, forcing one NodeRef into every subsystem is not automatically simpler if one lookup cannot resolve it.
Stream-away and reload tests
After ordinary activation works, test independently:
- load a named pre-activation seed outside the finite descriptor;
- approach normally and verify one activation;
- stream away by ordinary movement, return, and check reacquisition without duplication;
- load a separate save captured while the world-dependent stage is active;
- finish and verify cleanup;
- reload a completed save and confirm the root guard prevents reactivation.
Fast travel is a separate stress case because it can cross streaming and quest activation boundaries in one transition. Do not treat a normal-approach pass as proof of fast-travel safety.
Avoid speculative fixes
- Do not add both local and full forms to arbitrary fields.
- Do not duplicate
phasePrefabsacross root and child without an evidence-matched ownership policy. - Do not move a marker into AlwaysLoaded merely because one Quest-sector test used the wrong bounds.
- Do not rename a persistent device NodeRef without deciding whether the old saved identity should be abandoned.
- Do not copy a vanilla sector into the mod; cite and inspect it, then author mod-owned resources.
Previous: Handles, sockets, and resources. Next: Actors, scenes, and lipsync.
Actors, scenes, and lipsync
A scene-launch failure can occur before any subtitle, audio, or visualizer appears. Diagnose actor readiness, acquisition identity, scene graph entry, and indexed resource tables in that order.
Locate the launch boundary
Record the last confirmed event and the first missing event:
community Activate
-> CharacterSpawned passed
-> broad setup trigger passed
-> scene node entered
-> scene entry resolved
-> actor acquisition
-> section/event start
-> subtitle / WEM
-> named scene exit
If the game crashes before dialogue visualization and the crash follows the scene launch trigger when that trigger moves, focus on scene initialization. If bypassing the scene lets the world/community route continue, do not rebuild the world sector first.
Actor readiness workflow
For each required actor, compare all identity fields:
| Layer | Community actor | Player actor |
|---|---|---|
| Spawn owner | Active community entry/phase | Existing player context |
| Quest readiness | CharacterSpawned condition on the same community target | Usually not a community-spawn gate |
| Scene acquisition | community with matching area/entry | findInContext with the intended player record |
| Scene actor ID | Actor-table key used by screenplay lines | Separate actor-table key |
| Performer ID | Section/event/debug-symbol performer | Must map to the correct actor |
Then test this order:
- activate the community;
- wait for
CharacterSpawnedon the exact community NodeRef/entry scope; - enter a broad setup boundary;
- checkpoint if the tested pattern requires it;
- start the scene;
- let narrower scene conditions control approach beats.
A single fan-out that activates the community, waits, and starts the scene does not enforce that order. Starting only at a narrow engage boundary can also make several already-true scene conditions cascade in one tick.
Readiness and lipsync are independent. Legacy research repaired a fast-approach readiness gap, yet a deterministic launch crash remained until the lipsync table was corrected.
Audit actor and performer joins
For every spoken line or section:
- confirm the screenplay item’s speaker and addressee are valid actor IDs;
- confirm the section’s actor behavior rows use the intended performer IDs;
- confirm debug-symbol performer mappings agree with the actor table;
- confirm a community actor’s area NodeRef and entry name match the active registry entry exactly;
- confirm the scene marker exists and the quest scene node uses the correct entry point CName;
- confirm the quest expects an actual named scene exit, not a guessed
end.
Actor ID, performer ID, screenplay item ID, localization RUID, event ID, and CR2W handle ID are different domains. A numerically plausible reuse is not a join.
Audit lipsync cardinality
For each actor, record lipsyncAnimSet.id. Then inspect the cooked scene’s
native resouresReferences.lipsyncAnimSets array:
actor slot IDs: 0, 1
addressable rows after cooking: 1
result: invalid index 1
Require every selected index to be addressable. Two identical depot paths in the authoring view may collapse to one import; inspect the round trip and cooked imports, not only the source list.
The safest production correction is to provide the intended distinct, valid NPC and V resources and prove both rows remain addressable after cooking. A shared slot can isolate a cardinality fault, but it leaves facial and gesture quality Experimental unless separately accepted.
Head deformation during speech
If the actor looks normal before a line but stretches while speaking, inspect the selected facial clip before rebuilding the appearance. A clip can import and match the audio duration while lacking the skeletal reference its additive animation needs. Check facial keys, neutral skeletal channels, rig/track ordering, and the export from the cooked asset. See Custom clip checks for the fields to compare.
If the defect is also present at idle, investigate appearance, skinning, parent bindings, and rig compatibility. Treat a faint surface mark separately from severe animated mesh deformation; screenshots alone may not distinguish hair or cyberware clipping from a rendering artifact.
Reduce a crashing scene without losing causality
Use staged candidates, one change each:
- Keep world/community flow and replace the scene invocation with direct phase continuation. If that passes, the earlier world path is not the immediate crash boundary.
- Restore scene invocation with Start-to-End only. If it passes, root scene loading and entry/exit can work; actor readiness under full dialogue is not yet proved.
- Restore one actor-acquired section without audio/choices.
- Verify resource-table cardinalities and actor joins.
- Restore the line and external localization/audio path.
- Add choices, animation, interruption, and cleanup as separate surfaces.
Hash each candidate and reuse the same eligible starting-save lineage. Do not call a bypass “fixed”; it only locates a boundary.
Symptom routing
| Symptom | Inspect first |
|---|---|
| Actor never appears | Community registry/entry/phase, AI spot, activation, record/appearance namespace |
| Actor appears but scene waits forever | CharacterSpawned target/scope and scene acquisition entry |
| Crash exactly when scene launches | Actor table, performer joins, resource-reference indexes, graph entry |
| Slow approach works, fast approach fails | Readiness/order race and trigger cascade |
| Dialogue plays but actor is invisible | Character root/default appearance and requested community appearance |
| Head stretches during a line | Selected lipsync clip, additive skeletal reference, and rig/track ordering |
| Scene exits immediately | Bypassed section or retained Start-to-End edge |
| Scene never returns to quest | Scene End/named exit and questphase output CName mismatch |
| No subtitle or audio but no crash | Continue with Journal, localization, and audio |
Previous: NodeRefs, streaming, and placement. Next: Journal, localization, and audio.
Journal, localization, and audio
Journal UI, spoken dialogue, and embedded scene choices use different lookup systems. A fix in one does not repair the others.
Identify the lookup path
| Player-facing content | Authoritative key | Runtime owners |
|---|---|---|
| Quest title, objective, mappin, message, shard, onscreen | Textual secondary key | Journal entry plus ArchiveXL-registered onscreen localization |
| Spoken subtitle | Unsigned numeric localization RUID | Scene line plus subtitle map plus subtitle entries |
| Spoken audio | Same unsigned numeric RUID | Scene line plus VO map plus WEM ResourcePath |
| Player choice label | Scene-local numeric locstring ID | Choice option to screenplay option to embedded locStore descriptor/payload |
The screenplay item ID is not a localization RUID. The caption CName on a
choice is useful authoring/debug text but is not the authoritative rendered
label.
Journal entry is missing or has the wrong state
Trace one entry from definition to operation:
- Confirm the journal resource is packed and registered under the journal registration class.
- Copy the exact full
gameJournalPath.realPathfrom the quest node. - Split the path into components and verify each parent/child ID in the journal tree.
- Verify
fileEntryIndexidentifies the component represented by the containinggameJournalFileEntry; it is not the objective’s child index. - Confirm the quest edge enters the correct journal node socket, such as
Active,Succeeded,Failed, orInactive. - Check the starting save for an existing state or visited flag.
- Confirm the localized key exists only after the state transition itself is known to occur.
| Symptom | Likely first boundary |
|---|---|
| No entry and no state transition | Registration, path, fileEntryIndex, or graph socket |
| Entry exists with blank title | Onscreen localization registration/key |
| Objective activates but pin does not | Mappin journal child/state and world marker lookup |
| Entry is already succeeded on first test | Starting save or an earlier candidate’s graph state |
| File/shard exists but read gate never advances | Exact entry path, visited-state condition, and save lineage |
Do not rewrite localization while the journal node itself never executes.
Journal/UI text is blank
Compare the journal field and onscreen entry character for character:
journal localized field secondary key
-> registered en-us onscreen resource
-> localizationPersistenceOnScreenEntry.secondaryKey
-> femaleVariant / maleVariant text
Then inspect:
- correct
localization.onscreenslocale registration; - exact packed resource path;
primaryKey/secondaryKeyshape used by the focused lab;- duplicate secondary keys in the installed candidate set;
- current save state, since an inactive journal entry cannot display its text.
Do not convert a textual onscreen key into a scene RUID. They are separate domains.
Subtitle is missing
For one spoken line, make a four-column audit:
| Join | Expected equality |
|---|---|
| Scene screenplay line | locstringId.ruid equals the authored spoken-line RUID |
| Subtitle map | Registered map points to the exact subtitle-entries ResourcePath |
| Subtitle entry | stringId equals the scene RUID |
| Locale | Map is registered for the locale being tested |
Check ArchiveXL logs for the map registration and missing nested path. A subtitle map can register while its entries resource is absent from the archive.
Subtitle works but audio is silent
Keep the proven subtitle join unchanged and audit the VO branch:
- confirm
localization.vomapsregistration for the tested locale; - confirm the VO entry
stringIdequals the scene RUID; - confirm female and male ResourcePaths intentionally target existing WEMs;
- list/extract the archive and hash each WEM;
- confirm the game follows the expected gender path;
- compare format/provenance with the chapter’s known synthetic or authored source, without substituting extracted vanilla audio.
A present WEM is not proof of a valid VO-map join, and a valid VO-map join is not proof that the WEM encoding is accepted. Change one boundary per candidate.
Choice label is blank, stale, or belongs to another option
Trace the complete scene-local chain:
choice node option screenplayOptionId
-> screenplayStore.options[].itemId
-> option locstringId
-> locStore.vdEntries matching locale, ID, and signature
-> descriptor vpeIndex
-> locStore.vpEntries[vpeIndex]
Then verify:
- every option ID resolves to one screenplay option;
- descriptor locale blocks are contiguous in the evidence-matched order;
locstringIdvalues are sorted numerically as unsigned integers within the arrangement being copied, not lexicographically as decimal strings;- duplicate descriptors preserve their intended relative mapping;
- every
vpeIndexremains zero-based and points to the matching payload; - descriptor and payload
variantIdvalues match without signed truncation.
Legacy evidence showed a scene whose correct captions coexisted with blank and stale rendered labels. Round-trip serialization preserved the malformed order; it did not repair it. Treat ordering as authored data.
Localization isolation matrix
| Test | What a pass proves | What remains unproved |
|---|---|---|
| Journal state changes with placeholder-visible debug inspection | Graph path/state operation | Onscreen localization and player presentation |
| Onscreen title/objective renders | Journal/UI lookup | Scene subtitle, audio, and choices |
| Subtitle renders with audio disabled/unchanged | Scene-to-subtitle path | VO-map/WEM path |
| WEM plays with matching subtitle | Spoken-line external joins | Choice locStore |
| Every choice label maps correctly | Embedded locStore chain | Branch behavior and scene cleanup |
After fixing a lookup, repeat the scene or journal route from a save eligible for that transition. A save that already visited the message or passed the scene node cannot prove first presentation.
Previous: Actors, scenes, and lipsync. Next: Save state and clean retests.
Save state and clean retests
The save is part of the candidate. Replacing an archive does not rewind facts, journal state, active quest nodes, checkpoints, scenes, communities, or device persistent state.
Choose the right save class
| Save class | Suitable use | Not suitable for |
|---|---|---|
| Untouched pre-install original | First activation and clean replay | Routine play or destructive diagnosis |
| Closed-game full-slot clone of the original | Independent clean cases | Claiming a mid-flow resume state |
| Named mid-flow seed made under one exact candidate | Reload/stream/interruption cases defined at that point | First-install claims or a different candidate hash |
| Completed seed | Guard/reinstall/removal isolation | Replaying first activation |
| Old or unknown save | Negative diagnosis only | Positive acceptance evidence |
An eligible original is created before any version of the scoped quest was installed or registered. Preserve it unchanged and run tests on clones.
Clone a save safely
- Exit to desktop and wait for the game process to close.
- Record the source slot-directory name and visible save label.
- Hash the source slot’s canonical save artifact and record any metadata used by the lab acceptance form.
- Copy the complete slot directory, not selected files, to a new unique slot.
- Hash the clone and verify it matches the source capture before launch.
- Assign that clone to one execution only.
- Never refresh a failed case by copying files while the game is running.
Do not publish private save files. Retain hashes, labels, slot names, and privacy-reviewed metadata or notes sufficient to identify the input.
Inventory state domains before editing
| Domain | Example symptom after replacement |
|---|---|
| Fact database | Completion guard still bypasses the quest |
| Journal | Objective remains active/succeeded or a message stays visited |
| Quest graph/checkpoint | Execution resumes after the edited node instead of entering it |
| Scene | Active/interrupted state restores an older section or actor acquisition |
| Community | Entry remains active, spawned, or already cleaned up |
| Streaming world | Active world-dependent stage restores at a different load boundary |
| Device persistent state | Controller remembers interaction under the same NodeRef identity |
Resetting one fact does not clear the other rows. A save editor or console is useful for a clearly labelled diagnostic, but its result is not a clean-save acceptance case.
Diagnose “works only on an old save”
Run a two-input comparison without changing installed bytes:
- Hash the exact installed candidate and logs.
- Load a clone of the untouched pre-install original and follow the shortest route to the symptom.
- Exit fully; preserve observations and fresh logs.
- Relaunch the same bytes and load the old/unknown save.
- Compare the first visible divergence in facts, journal, active objective, actor/world state, or device state.
| Result | Interpretation |
|---|---|
| Clean clone passes, old save fails | Saved lifecycle is the leading boundary; do not rewrite structurally identical resources yet |
| Both fail at the same point | Save contamination is not sufficient to explain the failure |
| Old save passes, clean clone fails | Old state may bypass a broken first-run route |
| Results differ across launches with the same save and bytes | Preserve logs; investigate timing/streaming or duplicate installed resources |
An old completed save can make a broken first-run graph look healthy by taking the completion guard directly to termination.
Minimum lifecycle matrix
For a one-shot root, retain independent cases for:
- Clean activation: untouched clone enters the first-run route once.
- Mid-flow reload: a named seed resumes without duplicate activation or skipped required work.
- Completed reload: completion state persists and the root guard bypasses replay.
- Identical reinstall: reinstalling byte-identical candidate files does not erase completion.
- Clean replay: another clone of the original repeats the first-run route.
- Removal isolation: where the lab defines it, remove only the canonical candidate with the game closed and load an appropriate clone to prove the observed behavior depends on those mounted files.
World, scene, community, device, interruption, and stream-away cases add separate seeds and oracles. Do not claim them from the five core cases or the optional removal-isolation control.
When a fresh NodeRef is also required
A clean save is the primary test for a stateful device. When a revision deliberately changes the device’s persistent identity or invalidates prior controller state, author a fresh mod-owned NodeRef and test from a clean save. Record that this is identity migration, not a generic fix for every device problem.
Reusing the old NodeRef is appropriate when testing compatibility with old state. Renaming it merely to escape an unexplained failure discards that compatibility question.
Keep candidate and save lineage together
Every runtime row should identify:
- archive and loose-file hashes;
- full required/absent mod inventory;
- framework and game versions;
- source capture and execution clone;
- expected and observed result;
- complete relevant log hashes;
- retained privacy-reviewed evidence;
- whether the run passed, failed, or could not be executed.
A seed created under candidate A is not positive evidence for candidate B, even if their filenames match. If bytes change, regenerate the seed through the documented route or explicitly test compatibility as a different case.
Safe reset order
- Preserve the failing candidate, save metadata, and logs.
- Close the game.
- Restore the exact intended installed files and verify hashes.
- Restore or clone the correct save capture.
- Launch once and collect a fresh complete log bundle.
- Run only the assigned case.
Changing frameworks, candidate bytes, unrelated mods, and starting saves together destroys the comparison.
Previous: Journal, localization, and audio. Next: Controlled isolation and evidence.
Controlled isolation and evidence
Controlled isolation answers one question: what smallest changed boundary explains the different result? It requires a known-good or known-failing baseline, one intentional delta, the same eligible inputs, and retained evidence for both sides.
Freeze the baseline
Before creating a candidate, record:
| Input | Frozen value |
|---|---|
| Game/framework stack | Exact versions and storefront/build |
| Candidate files | Names, sizes, SHA-256 hashes, and install paths |
| Archive contents | Sorted depot-path listing and extracted payload hashes |
| Unrelated mods | Explicit inventory or verified absence |
| Starting save | Capture name, slot directory, pre-install assertion, and hash |
| Route | Exact player actions and activation boundary |
| Oracle | Visible/log result that distinguishes pass from fail |
| Logs | Complete file paths and hashes |
“Same setup” is not evidence unless these values are retained.
State one falsifiable hypothesis
Write the hypothesis before editing:
If actor slot 1 is the out-of-range lookup, then changing only the scene's
addressable lipsync table/consumer indexes will move the launch result from the
same crash to successful scene entry under the same route and save class.
A useful hypothesis names:
- one suspected boundary;
- one controlled change;
- one expected observation if correct;
- one observation that would reject it.
“Make the scene more vanilla” is not falsifiable. “Change actor readiness, trigger radius, lipsync rows, VO paths, and graph timing” cannot identify a cause.
Build a one-delta candidate
- Copy the working baseline project.
- Make one semantic change. Generated binary noise does not count as another semantic change, but it must still be inspected.
- Round-trip every changed CR2W and compare decisive properties.
- Build in a clean staging directory.
- List/extract the archive and prove the expected payload set.
- Compare extracted payloads with the baseline and name every changed depot path.
- Stage one canonical candidate; record that earlier labs/copies and unrelated mods are absent.
- Hash installed files after staging.
If more payloads changed than planned, stop. Explain or eliminate the drift before runtime testing.
Run paired cases
Use independent full-slot clones from the same eligible capture:
| Run | Installed bytes | Save clone | Purpose |
|---|---|---|---|
| Baseline reproduction | Baseline hash | Clone A | Prove the original symptom still occurs |
| Candidate test | Candidate hash | Clone B | Test the one-delta hypothesis |
| Baseline return, when practical | Baseline hash restored | Clone C | Reject an unrelated one-time environment change |
| Removal isolation, when defined | Candidate pair deliberately absent | Clone D | Prove mounted candidate dependency without mutating save state |
Close the game before changing files or cloning slots. Run the same route and collect a fresh full log bundle each time.
Record observations before explanations
Separate these fields:
| Field | Example |
|---|---|
| Expected | Scene reaches its first section without process termination |
| Observed | Crash at setup boundary; no dialogue visualizer; report ID retained |
| Difference | Candidate moved past setup and returned through named exit |
| Interpretation | Result is consistent with the indexed table diagnosis |
| Rejected alternatives | World/trigger path was byte-identical; subtitle/VO resources unchanged |
| Limitations | Shared slot is diagnostic; facial quality and reload not tested |
Do not rewrite “consistent with” into a universal engine law. Runtime evidence is bounded by the exact bytes, versions, save class, and route.
Use negative controls
Negative controls prevent false attribution:
- bypass a suspect scene while leaving world/community setup intact;
- invoke a Start-to-End shell before restoring dialogue;
- remove only the candidate pair while keeping framework/unrelated-mod inventory frozen;
- restore the baseline hash after a passing candidate;
- test a clean original and an old save with identical installed bytes;
- move an activation boundary without changing the suspected resource, then record whether the failure follows launch or world position.
Each control answers a narrow question. A scene bypass proves the preceding path can continue; it does not prove the full scene is correct.
Keep a reproducible test
Save the baseline project, installed files, starting save, versions, and logs. Note the single change, the route you followed, and what happened. Keep failed runs too, so later changes can be compared with the same baseline.
If several changes were made together, split them into separate tests before assigning a cause.
Handoff checklist
Before another tester receives the candidate, they should be able to answer:
- Which exact bytes should be installed?
- Which files and mods must be absent?
- Which save capture and clone should be used?
- What actions reach the tested boundary?
- What observation passes or fails the case?
- Which logs and evidence must be retained?
- Which neighboring claims are explicitly outside scope?
If any answer is missing, the next run may be informative, but it is not yet a reproducible acceptance case.
Previous: Save state and clean retests. Back to: Troubleshooting.
Reference
Use this section when you know the question but do not need another tutorial. It maps player-facing concepts to native resources, keeps identifier domains separate, and points each structural or runtime claim back to its evidence boundary.
Orientation
| Need | Reference |
|---|---|
| Choose the next practical project or download a checkpoint | Labs at a glance |
| Decode a RED or book-specific term | Glossary |
| Find which resource owns a responsibility | Resource map |
| Distinguish handles, NodeRefs, facts, paths, keys, and numeric IDs | Identifier-domain quick reference |
Graph and condition lookup
| Need | Reference |
|---|---|
| Find a quest/scene node, its sockets, and the lab that demonstrates it | Node and socket index |
| Choose a condition family without confusing a predicate with a waiting node | Condition index |
| Read node IDs, sockets, edges, and colors in the diagrams | Reading graph diagrams |
Registration and data lookup
| Need | Reference |
|---|---|
Build or diagnose a typed journal path and fileEntryIndex | Journal-path reference |
| Trace UI, subtitle, VO, or embedded scene-choice localization | Localization reference |
| Decide what ArchiveXL registers and what the archive reaches indirectly | ArchiveXL registration reference |
| Connect a status to a custom visual on V or an NPC | Entity spawners and status effects |
| Reuse the tutorial naming convention without colliding with it | Tutorial namespace |
Additional references
| Need | Reference |
|---|---|
| Check which labs have been tested in game | Lab status |
| Check the exact pinned practical versions | Tested versions |
| Locate a named vanilla resource to extract and inspect yourself | Vanilla depot paths |
The vanilla index contains depot paths and focused properties. Extract the resources from your own installation when inspecting them.
For failures rather than lookup, start with Troubleshooting.
Labs at a glance
The five labs form one progression from a registered root questphase to a community-backed spoken scene. Each lab is a complete, mod-owned WolvenKit project with a start checkpoint, a completed checkpoint, an exact structural contract, and an in-game test guide.
Use the title, namespace, and project name in this table consistently. Lab 1
is the reader-facing number; cqa001 is its resource namespace;
CQA_Lab01_OneShot is its completed WolvenKit and installed-file stem. Those
three names belong to different domains and are not interchangeable.
Reading sequence
| Lab | Canonical title and namespace | Primary boundary | Build and test |
|---|---|---|---|
| 1 | First Signal · cqa001 | Root registration, journal state, real-time delay, one-shot fact | Overview · Author · Test |
| 2 | Signal Race · cqa002 | Immediate selection, pause conditions, parallel listeners, XOR-shaped convergence | Overview · Author · Test |
| 3 | Boundary Check · cqa003 | Streaming block/sector ownership, NodeRefs, marker, nested trigger volumes | Overview · Author · Test |
| 4 | Handoff Point · cqa004 | Registered root, archive-resolved external child, In1/Out1 handoff | Overview · Author · Test |
| 5 | First Contact · cqa005 | Community lifecycle, actor acquisition, one-line scene, named exit, cleanup | Overview · Author · Test |
The topical table of contents places each lab beside the system it teaches, so Lab 4 appears under Questphases before the later-numbered topical sections. The table above is the canonical practical sequence.
For each lab, read its overview, follow Author, then follow Test. Use the intervening system chapters when you need an explanation of a field or behavior; you do not need to read every advanced recipe before continuing.
Checkpoint identity
| Lab | Completed project/file stem | Start ZIP | Completed ZIP |
|---|---|---|---|
| 1 | CQA_Lab01_OneShot | Download | Download |
| 2 | CQA_Lab02_SignalRace | Download | Download |
| 3 | CQA_Lab03_BoundaryCheck | Download | Download |
| 4 | CQA_Lab04_HandoffPoint | Download | Download |
| 5 | CQA_Lab05_FirstContact | Download | Download |
A start and completed checkpoint for the same lab own the same depot paths. They are alternative source states, not two mods to install together. Lab 2’s edited timing variant is another separately unchanged candidate. Install only the candidate named by the current test case, with the game and framework processes closed.
Test one lab at a time. Labs 3–5 share the Allen Street site, so leaving an earlier lab installed can add overlapping triggers, pins, and objectives. Preserve a save from before starting any tutorial: Lab 5 specifically requires that clean lineage, even if you have already completed Labs 1–4 elsewhere.
How to use the checkpoints
All five completed checkpoints pass the repository’s source, graph, package,
and WolvenKit 8.19.0 round-trip checks. Use them to compare resource types,
properties, graphs, paths, and registration with your own work.
All five labs still await in-game acceptance. Their intended runtime behavior is Experimental, including the completed checkpoints.
After authoring, run the linked test guide from the required starting save. Check normal play, streaming, cleanup, and reload as applicable.
Shared practical baseline
The practical guides use Cyberpunk 2077 2.31a, WolvenKit 8.19.0, ArchiveXL
1.27.0, RED4ext 1.30.0, and redscript 0.5.31. See Tested
versions for roles and boundaries. Every runtime campaign
also requires the clean-save and candidate-isolation rules in its own test
page; sharing a tool version does not make two save histories equivalent.
Next: Glossary.
Glossary
This glossary defines the terms used by the book. It is intentionally about native Cyberpunk 2077 resources and runtime systems. Names used only by a research tool are not part of the reader vocabulary.
The short definitions below route to the chapter that owns the complete contract. When a term names a resource shape, its runtime behavior still inherits the evidence label and version boundary stated by that chapter.
A–C
| Term | Meaning | Continue with |
|---|---|---|
| Actor | A scene role declared with scnActorDef or scnPlayerActorDef. Its scnActorId is scene-local and is not a performer ID, community ID, or TweakDB record. | Actors and performers |
| AI spot | A placed worldAISpotNode whose AIActionSpot refers to a workspot resource. Placement, world identity, workspot behavior, and actor selection have separate owners. | Entries, phases, and AI spots |
| Archive | The packed .archive that carries CR2W resources and other archived payloads at depot paths. Its existence does not prove that a loose ArchiveXL registration file was installed. | Resources and ownership |
| ArchiveXL registration | Loose configuration that attaches or merges a mod-owned resource at the appropriate game root, such as quest.phases, journal, localization, or streaming.blocks. It is different from a native soft reference between archived resources. | Registering a root questphase |
| Child questphase | A questphase invoked by a questPhaseNodeDefinition in another phase. The external-child form used in Labs 4–5 is archived at its depot path and resolved through phaseResource; it is not independently attached to the game quest root. | Calling child phases |
| Clean save | For the book’s focused start tests, a manual save made before any version of the candidate quest was installed or registered. Resetting a fact does not clear all journal, phase, scene, community, or device state. | Facts, journals, and saves |
| CName | A REDengine name token used for values such as socket names, entry points, community entries, and phases. A CName that prints as text is not therefore a fact, depot path, NodeRef, or TweakDBID. | Identifier domains |
| Community | The native world-side system that combines registry template data, a compiled area, entries, phases, AI spots, and quest-controlled activation to materialize actors. A scene can acquire an available community entry but does not create the community. | Communities and characters |
| Compiled community area | A streamed worldCompiledCommunityAreaNode_Streamable that places a community source and mirrors its entry/phase/time-period route to world-global AI-spot identities. | Registries and compiled areas |
| Condition | In graph prose, an immediate questConditionNodeDefinition that evaluates its predicate on entry and selects a result such as True or False. The nested condition object decides what is tested. | Immediate branches and waiting gates |
| Condition payload | The typed predicate beneath a Condition or Pause Condition node, often represented as a domain wrapper plus a concrete type handle. For example, questFactsDBCondition wraps questVarComparison_ConditionType. | Condition payloads |
| Connection | A graphGraphConnectionDefinition joining one source output socket to one destination input socket in a quest graph. Reciprocal handle references from both sockets still describe one edge. | Graph execution |
| CR2W | REDengine’s typed resource container used by questphases, journals, scenes, world resources, and many referenced assets. A successful parse or save proves structure was accepted, not that external references resolve in game. | Resources and ownership |
| CR2W handle | A serialization-local HandleId / HandleRefId relationship between objects in one CR2W object graph. It is not a quest node ID or NodeRef. | Identifier domains |
| CR2W-JSON | WolvenKit’s serialized review form for a CR2W resource. The book uses it for deterministic inspection and figures; readers author CR2W resources through WolvenKit rather than treating raw JSON editing as the beginner workflow. | Reading graph diagrams |
| Cut | An explicit interruption route from a CutSource to one or more CutDestination sockets. The presence of cut sockets does not establish cleanup, rollback, re-entry, or propagation behavior. | Parallel monitors and cancellation |
D–L
| Term | Meaning | Continue with |
|---|---|---|
| Depot path | A resource address in the game’s virtual depot, written with backslashes, such as mod\cqa\cqa005\scenes\cqa005_first_contact.scene. It is not a Windows filesystem path. | Resources and ownership |
| Descriptor | In the streaming chapters, a worldStreamingSectorDescriptor in a block. Its data points to a sector and its other fields carry streaming metadata such as bounds, category, level, and an optional quest-prefab root. | Streaming model |
| Entry point | A named .scene interface entry that maps a CName such as start to an scnNodeId. The caller’s questSceneNodeDefinition must expose a same-named input socket. | Entry, exit, and quest handoff |
| Evidence label | One of exactly four claim classes: Runtime-proven, Structurally validated, Observed in vanilla, or Experimental. | Claim labels |
| External child | The Lab 4–5 child-phase arrangement in which phaseGraph is null and phaseResource softly names another archived .questphase. It contrasts with an inline/in-place graph. | Questphase resource anatomy |
| Fact | A named signed-integer slot in the facts database. Authors commonly use 0 and 1 by convention, but the resource does not turn the fact into a Boolean. | Facts, journals, and saves |
| Fan-out | Several connections leaving one output socket. It starts several downstream routes and does not establish an order or a later join. | Graph execution |
| Graph-local node ID | The numeric id of a node inside one questphase or scene graph. Its scope must be recorded; another graph may reuse the same number. It is independent of CR2W handle allocation. | Identifier domains |
| Inplace resource | A resource stored or referenced through a containing resource rather than addressed as the same kind of standalone owner. Questphase inplacePhases, sector .streamingsector_inplace content, and node-local instanceData are separate domains despite the shared word. | Sector nodes and placement |
| Journal entry | A typed object in the merged journal tree, such as a quest, phase, objective, map pin, message, file, or onscreen. Its authored definition is separate from its save-backed runtime state. | Journal trees and typed paths |
| Journal path | A typed gameJournalPath containing realPath, className, fileEntryIndex, and editorPath. It identifies an entry in the merged journal tree, not a depot resource or CR2W object. | Journal trees and typed paths |
| Join | A graph topology where several execution routes converge. The RED node type and retained runtime evidence determine whether it waits, passes through, remembers arrivals, resets, or restores across saves. | Signal flow |
| Localization key | The identifier used by one localization system. Journal/UI secondary keys, spoken-line RUIDs, and embedded scene-choice RUIDs belong to different lookup paths. | Localization paths |
| Locstring RUID | The unsigned scnlocLocstringId.ruid used by a scene line or choice. For spoken lines it joins the scene to subtitle and VO records; it is not a screenplay item ID. | Localization paths |
M–R
| Term | Meaning | Continue with |
|---|---|---|
| Mappin | Map/HUD presentation backed by a typed journal entry and, for the quest-pin route taught here, a world marker NodeRef. The journal pin, world marker, and quest manager node are separate owners. | Mappins: journal intent meets the world |
| Native quest resource | The game’s own quest, scene, journal, localization, world, community, and related resource model. In this book, “native” does not mean RED4ext native-plugin development. | Resources and ownership |
| NodeRef | A typed world reference used to address a registered world object or a child beneath a quest-prefab root. It is not a CR2W handle, ResourcePath, or TweakDBID. | Quest prefabs and NodeRefs |
| Node data | A sector placement record, commonly worldNodeData, that carries transform, bounds, streaming metadata, NodeIndex, and QuestPrefabRefHash. It is distinct from the concrete node and nodeRefs[] registration. | Sector nodes and placement |
| Ordinals | Node-local values used to distinguish ordered sockets, options, or scene socket stamps. They do not define execution order for the whole graph. | Graph execution |
| Pause Condition | A questPauseConditionNodeDefinition that retains one execution path until its predicate is fulfilled, then emits Out. It is not an immediate false branch. | Immediate branches and waiting gates |
| Performer | A scene debug-symbol identity represented by scnPerformerId. It helps map editor-facing performer information but cannot replace the scnActorId used by screenplay and actor behavior. | Actors and performers |
| Phase prefab | A questQuestPrefabEntry in a questphase’s phasePrefabs array. It declares a quest-prefab root available to that composition; it does not place the referenced world content. | Prefab dependencies |
| Quest prefab | The world-reference namespace rooted at a prefab NodeRef and bound through a Quest-sector descriptor. In the workflow taught here it does not imply that a standalone .prefab file exists. | Quest prefabs and NodeRefs |
| Questphase | A .questphase CR2W resource rooted at questQuestPhaseResource. It coordinates execution, conditions, journal operations, facts, scenes, world references, and child phases; it does not create those external resources. | Questphase resource anatomy |
| Race | Several active execution routes converging on a winner-shaped node. An XOR-shaped topology is not evidence that losing listeners are automatically cancelled. | Signal flow |
| ResourcePath | The typed serialized value used to address another depot resource. A soft ResourcePath is a reference-loading policy, not ArchiveXL registration. | Identifier domains |
| Root questphase | The composition root attached beneath base\quest\cyberpunk2077.quest through ArchiveXL. A root still needs explicit re-entry, completion, and termination policy. | Registering a root questphase |
S–W
| Term | Meaning | Continue with |
|---|---|---|
| Save-backed state | Runtime state retained independently of a later archive edit, including facts, journal state, active quest work, scenes, communities, and some device state. | Facts, journals, and saves |
| Scene | A .scene CR2W resource rooted at scnSceneResource. It owns actors, screenplay, timed events, graph flow, entry/exit names, interruption policy, and referenced animation data. | Scene resource anatomy |
| Screenplay item | A line or option in scnscreenplayStore, identified by scnscreenplayItemId. A timed event points to a line item; the line separately points to its localization RUID. | Screenplay, sections, and events |
| Socket | A named quest-graph connection point with a role such as Input, Output, CutSource, or CutDestination. The name and the node type together define its local meaning. | Node and socket index |
| Socket stamp | A scene-graph name/ordinal pair used to connect scene nodes. It is a different representation from questSocketDefinition even when both are discussed as graph ports. | Entry, exit, and quest handoff |
| Soft reference | A resource reference whose flags are Soft, such as an external child phaseResource or a scene sceneFile. It still requires the target at the exact depot path and does not register it as a root. | Calling child phases |
| Streaming block | A worldStreamingBlock whose descriptors point to sectors and carry their streaming metadata. ArchiveXL registers the block, not each sector separately. | Streaming model |
| Streaming sector | A worldStreamingSector that owns or references concrete world nodes, placement records, and registered NodeRefs. Its arrays are not universally parallel. | Sector nodes and placement |
| Terminating output | A questOutputNodeDefinition whose output type is Terminating. Reaching it ends that phase route; it does not automatically reset state or clean external systems. | Inputs and outputs |
| TweakDBID | A typed identifier for a gameplay record in TweakDB, such as a character record. It is not an .ent depot path or a world NodeRef. | Entries, phases, and AI spots |
| WEM | A cooked audio payload resolved from a voiceover-map resource path. The WEM is archived, while the VO map is the ArchiveXL registration root for the spoken-audio branch. | Author one spoken line |
| WolvenKit | The editor and resource tool used by the reader to inspect, author, convert, pack, and deploy the resource shapes taught by the book. Documentation-author scripts are not reader prerequisites. | Set up from zero |
| Workspot | A referenced resource that supplies an AI activity or animation setup. A worldAISpotNode supplies placement and points to it; the workspot does not choose the community actor or world transform. | Entries, phases, and AI spots |
Claim labels
Use the narrowest label supported by the retained evidence:
| Label | Meaning |
|---|---|
| Runtime-proven | The stated bounded behavior or result was observed in game for the exact retained arrangement. The observed result can be a success or a failure; a failed campaign remains Experimental for the intended behavior that did not occur. The claim remains bounded by its versions, hashes, save lineage, and tested cases. |
| Structurally validated | The resource serialized, round-tripped, and passed the stated structural checks, but the claimed runtime behavior has not been established for that arrangement. |
| Observed in vanilla | The focused shape occurs in one or more cited game resources. It is a research precedent, not automatic proof that a new mod-owned arrangement works. |
| Experimental | The investigation, resource contract, or required runtime acceptance remains incomplete. |
Labels apply to the behavior beside them. A working first run does not establish interruption or reload behavior.
Resource map
Use this page to answer two questions before editing a graph:
- Which resource owns the missing definition or behavior?
- Which registration or native reference makes that owner reachable?
The Lab 1–5 resources are Structurally validated with WolvenKit 8.19.0.
Rows marked Observed in vanilla describe game resources to inspect yourself.
Project and installation layers
| Project path or artifact | Owns | Installed or consumed by | Do not confuse it with |
|---|---|---|---|
source\archive\ | Complete CR2W resources and archived non-CR2W payloads at their intended depot paths | WolvenKit packing/deployment produces a game .archive | Loose ArchiveXL configuration |
source\resources\ | Loose framework configuration such as <project>.archive.xl | Deployed beside the archive to the framework’s expected game path | A CR2W resource inside the archive |
source\raw\ | WolvenKit CR2W-JSON review artifacts for mod-owned resources | Documentation review, round trips, diagrams, and semantic comparison | Runtime payloads or the beginner authoring format |
*.cpmodproj | WolvenKit project metadata and source-root configuration | WolvenKit | A depot resource or registration file |
*.archive | Packed runtime payload container | Cyberpunk 2077’s resource loader | Proof that ArchiveXL saw a loose registration file |
*.archive.xl | ArchiveXL merge and registration instructions | ArchiveXL | The packed archive or a native soft reference |
| Lab ZIP | Downloadable checkpoint assembled from the project | Reader extraction | An installed mod; start and completed alternatives must not coexist |
The same depot path can appear as a file below source\archive, a
ResourcePath inside another CR2W, a registration value, and a runtime log
entry. A path below source\raw normally has an extra conversion suffix and
must not be registered.
Native control, journal, and scene resources
| Resource | Root or decisive native type | Owns | Becomes reachable through | Coverage |
|---|---|---|---|---|
*.questphase | questQuestPhaseResource | Quest graph, phase interface, condition scheduling, fact/journal operations, scene and child handoff, prefab declarations | A root uses ArchiveXL quest.phases; an external child uses the parent’s soft phaseResource | 1–5 |
*.journal | gameJournalResource | The contributed journal-entry tree: quests, phases, objectives, map pins, messages, files, and related entries | ArchiveXL journal merge | 1–5 |
*.scene | scnSceneResource | Actors, screenplay items, choices, timed events, ordinary or rewindable graph flow, entry/exit names, interruption policy, and animation references | A questSceneNodeDefinition.sceneFile soft reference; it is not a quest-root registration | Lab 5 plus advanced scenes |
*.scenerid | scnRidResource | Recorded actor/camera tags, channel serials, animation buffers, and RID-local allocation | A scene scnRidResourceHandler, then scene-local RID reference arrays and timed events | Advanced braindance; structurally validated candidate, runtime Experimental |
Important ownership boundaries:
- A questphase journal node changes state at a typed path; it does not define the target entry.
- A root registration does not register external children or scenes as independent quest roots.
- A
.sceneowns its namedcontact_doneexit; the calling questphase owns the same-named output socket and the continuation after it. - Termination ends a graph route. It does not automatically clean a marker, community, scene, journal entry, or fact.
See Questphase resource anatomy, Journal trees and typed paths, and Scene resource anatomy.
Localization and audio resources
Several different CR2W resource types use the depot suffix .json. Inspect
the root type and registration branch instead of treating the suffix as an
ownership contract.
| Player-facing content | Resource type | Owns the decisive lookup | Registration or reference |
|---|---|---|---|
| Journal and onscreen UI | JsonResource containing localizationPersistenceOnScreenEntries | Text entries keyed by textual secondaryKey | ArchiveXL localization.onscreens.<locale> |
| Spoken subtitles: map | JsonResource containing localizationPersistenceSubtitleMap | Map entries whose subtitleFile points to subtitle-entry resources | ArchiveXL localization.subtitles.<locale> |
| Spoken subtitles: entries | JsonResource containing localizationPersistenceSubtitleEntries | Subtitle text keyed by unsigned stringId | Reached through the registered subtitle map; not registered in its place |
| Spoken voice: map | JsonResource containing locVoiceoverMap | locVoLineEntry rows joining the same stringId to female/male audio paths | ArchiveXL localization.vomaps.<locale> |
| Spoken voice: audio | *.wem | Cooked audio bytes | Archived at the femaleResPath / maleResPath named by the VO map |
| Scene choice text | Embedded scnlocLocStoreEmbedded inside *.scene | Descriptor/payload tables joined from screenplay choice RUIDs | No ArchiveXL localization branch; the scene owns it |
The subtitle map and VO map are registration roots. The subtitle-entry file and WEM are referenced leaves. Registering a leaf where a map is expected does not complete the lookup.
For the complete joins and locale spelling differences, use Localization paths. Lab 5’s externally localized one-line route is explained in Author one spoken line.
World and streaming resources
| Resource | Root or decisive native type | Owns | Becomes reachable through | Evidence boundary |
|---|---|---|---|---|
*.streamingblock | worldStreamingBlock | Sector descriptors, sector paths, streaming boxes, categories, levels, and quest-prefab roots | ArchiveXL streaming.blocks | Lab 3–5 shapes are Structurally validated |
*.streamingsector | worldStreamingSector | Concrete world nodes, placement/cooked metadata, registered NodeRefs, and references to inplace content | worldStreamingSectorDescriptor.data in a registered block | Lab 3–5 compact shapes are Structurally validated |
*.streamingsector_inplace | worldStreamingSectorInplaceContent | An inplaceResources[] collection with corresponding embedded CR2W bodies represented separately in the serialized container | A sector’s local or external inplace-resource relationship | Observed in vanilla here; not supplied by Labs 1–5 |
Base *.streamingworld | worldStreamingWorld | References compiled blocks and broader global world resources | Game-owned world root | Observed in vanilla; inspect, do not replace or redistribute |
The compact placed-node chain used by the labs is:
registered streaming block
-> descriptor.data
-> streaming sector
-> concrete nodes[] object
+ nodeData placement and QuestPrefabRefHash
+ nodeRefs[] identity registration
The three sector collections are related but not universally parallel.
nodeData.NodeIndex is the explicit association in the compact mod-owned
shape; array position is not a schema rule. See Sector nodes and
placement.
Community and character resources
Lab 5 embeds its community template data in a world registry node rather than
shipping a standalone .community resource.
| Owner | Native type or resource | Responsibility | Discovery path |
|---|---|---|---|
| Registry node | worldCommunityRegistryNode in an AlwaysLoaded sector | Community template, initial entry state, persistent AI-spot data, and the registry side of the community identity join | Concrete sector node plus placement/identity records |
| Compiled area | worldCompiledCommunityAreaNode_Streamable in a Quest sector | Streamed source placement and entry/phase/time-period mirror to AI-spot IDs | Concrete sector node plus placement/identity records |
| Community entry | communitySpawnEntry inside communityCommunityTemplateData | Entry CName, character TweakDB record, quantity, and spawn phases | Embedded in the Lab 5 registry item |
| Spawn phase | communitySpawnPhase and communityPhaseTimePeriod | Phase, appearance, period, sequencing, and spot references | Embedded beneath the community entry |
| AI spot | worldAISpotNode containing AIActionSpot | Placed activity and soft workspot resource path | Quest sector placement and NodeRef registration |
| Standalone community | *.community rooted at communityCommunityTemplate | Reusable community template data in the cited vanilla examples | Observed in vanilla comparison source; not a Lab 5 dependency |
| Character record | TweakDBID such as Character.jpn_tyger_claws_biker2_ranged2_copperhead_wa | Gameplay record that leads onward to a character/template dependency chain | TweakDB; it is not a depot path |
| Entity template | *.ent, commonly rooted at entEntityTemplate | Template-specific components, slots, appearances, interactions, and other entity composition | Referenced from records or world entity nodes; advanced authoring is bounded by the compatible mod-owned-shell requirement |
| Appearance resource | *.app rooted at appearanceAppearanceResource | Internal appearance definitions and their component graphs | An entity template’s exposed appearance mapping; advanced custom behavior remains Experimental |
| Workspot | *.workspot | Referenced activity/animation behavior | worldAISpotNode.spot.resource; the AI spot still owns placement |
The exact Lab 5 join is developed in Registries and compiled areas and Entries, phases, and AI spots. The advanced chain and its legal-shell boundary are in Character records, entities, and appearances, while AI roles, behavior, and workspots separates actor, role, route, and activity ownership. Never add an extracted vanilla CR2W to a downloadable project.
Referenced world and device surfaces
These owners form the advanced device research model. Their linked procedure is complete as an Experimental authoring and acceptance boundary, not as a generic Runtime-proven custom-device recipe:
| Surface | Owner | Boundary |
|---|---|---|
| Placed entity or device | worldEntityNode or worldDeviceNode | Refers to an entity template and may carry node-local instanceData |
| Node-local controller/component state | entEntityInstanceData leading to a RedPackage | Different from sector inplace content |
| Global device tables | *.devices and *.psrep | Context-dependent; absence from one focused vanilla flow is not a universal no-registration rule |
| Persistent device identity | Save plus world identity/NodeRef | A materially changed device may require both a clean save and deliberate identity policy |
See Devices and persistence and Advanced devices and interactions before using any of these surfaces. The current Labs 1–5 do not supply a custom device.
Registration versus native reference
This is the quickest lookup for “which file do I put in ArchiveXL?”
| Target | ArchiveXL registration? | Native reference that reaches non-root dependencies |
|---|---|---|
| Registered root questphase | Yes: quest.phases | Parent is base\quest\cyberpunk2077.quest |
| External child questphase | No independent root entry | Parent questPhaseNodeDefinition.phaseResource |
| Journal contribution | Yes: journal | Quest nodes then use typed gameJournalPath values |
| Onscreen localization | Yes: localization.onscreens.<locale> | Journal/UI LocalizationString.value joins to secondaryKey |
| Subtitle map | Yes: localization.subtitles.<locale> | Map entry subtitleFile reaches subtitle entries |
| Subtitle entries | No direct registration in this route | Registered subtitle map |
| VO map | Yes: localization.vomaps.<locale> | Its line entries reach WEM resource paths |
| WEM | No direct registration | VO-map femaleResPath / maleResPath |
| Streaming block | Yes: streaming.blocks | Its descriptors reach sectors |
| Streaming sector | No separate block registration | Descriptor data resource reference |
| Scene | No quest-root registration | questSceneNodeDefinition.sceneFile |
| Scene RID or animation-set leaf | No independent quest-root registration | Scene RID handlers and resouresReferences arrays |
| Mod-owned character record | Not through ArchiveXL; use the locally pinned TweakXL 1.11.3 route | Record entityTemplatePath reaches the archived .ent |
| Mod-owned entity/appearance resource | Archived at a unique depot path; no root merge | Character record reaches .ent; entity appearance mapping reaches .app |
| Vanilla workspot or animation set | No new registration in the taught route | AI spot or scene resource-reference path |
Custom visual effects
| Resource or record | Owns | Reached through |
|---|---|---|
| TweakDB status record | Duration and VFX/SFX requests | Item, quickhack, or other gameplay action |
StatusEffectFX record | Requested effect CName | Status record’s VFX array |
entEffectSpawnerComponent / entEffectDesc | Effect-name lookup, timeline path, event placements | Component on the target entity; add with an ArchiveXL resource.patch when needed |
.effect / worldEffect | Particle/sound tracks, loop range, outro | Descriptor’s effect reference |
.particle / CParticleSystem | Emitters, drawer, authored and compiled simulation | effectTrackItemParticles.particleSystem |
Local CMaterialInstance | Texture references, tint, opacity, blending | Emitter’s localMaterialInstance |
.xbm textures | Color and mask pixels | Material’s Diffuse and AlphaMask references |
The VFX record name, effect CName, component name, and effect depot path are different identifiers. Follow Custom effects to connect them and test the screen and NPC variants separately.
Save-state overlay
Resource ownership and saved runtime state overlap but are not the same layer:
| Definition in installed resources | State that can remain in the save |
|---|---|
| Fact names and graph writers/readers | Current fact integer values |
| Journal entries and typed paths | Active, inactive, succeeded, failed, visited, and tracking state |
| Questphase nodes and edges | Active phase work and checkpoints |
| Scene graph, actors, and exits | Active or completed scene state |
| Community registry, area, and spots | Activation and actor lifecycle state |
| Device template and instance data | Persistent controller/device state associated with identity |
Repacking a definition cannot be used as evidence that the corresponding saved state was reset. Route stale or contradictory retests through Facts, journals, and saves before changing more resource fields.
Node and socket index
This is a search index for the graph nodes and socket names introduced through Labs 1–5 and the supporting control-flow chapters. It is not a replacement for the node’s complete property contract.
The exact node sets in the completed lab projects are Structurally
validated with WolvenKit 8.19.0. Wider arities and node families explicitly
described as vanilla inventory are Observed in vanilla. Runtime scheduling,
cancellation, save restoration, and re-entry remain governed by each lab’s
retained evidence record. Start with Labs at a glance
when choosing the smallest project that contains a node.
Read a quest node in four parts
For every quest graph node, record:
- the graph-local numeric
idand containing.questphase; - the concrete RED node type;
- the decisive typed payload or external reference;
- every connected socket name, socket type, and destination.
The node’s serialized array position and CR2W HandleId are not execution
order. One output socket can contain several connections, and reciprocal
references from the source and destination still describe one edge.
Quest graph boundary and state nodes
Socket sets below are the focused shapes used by the book. WolvenKit can omit unused ordinary sockets from one serialized instance, and some node types add dynamic sockets. Confirm the exact resource rather than manufacturing every name in this table.
| WolvenKit label / RED type | Job | Decisive property or payload | Focused sockets | First complete use |
|---|---|---|---|---|
Input / questInputNodeDefinition | Expose one phase entry | socketName, such as In1 | internal Out; CutDestination | Lab 1 |
Output / questOutputNodeDefinition | Expose a phase result and optionally terminate | socketName, such as Out1; output type | internal In; CutDestination | Lab 1 |
Condition / questConditionNodeDefinition | Evaluate a predicate on entry and branch immediately | condition handle | In, True, False, CutDestination | Lab 1 |
PauseCondition / questPauseConditionNodeDefinition | Hold one active route until a predicate is fulfilled | condition handle | In, Out, CutDestination | Lab 1 |
Journal / questJournalNodeDefinition | Request a state change for one typed journal path | type handle, including path and presentation properties | Active, Inactive, Succeeded, Failed inputs; Out; CutDestination | Lab 1 |
FactsDBManager / questFactsDBManagerNodeDefinition | Write a fact | questSetVar_NodeType, including fact name, value, and exact/add policy | In, Out, CutDestination | Lab 1 |
MappinManager / questMappinManagerNodeDefinition | Request active/inactive state for a journal quest pin | Typed pin path; disablePreviousMappins | Active, Inactive, Out, CutDestination | Lab 3 |
Phase / questPhaseNodeDefinition | Invoke an inline or external child phase | External form: phaseResource, null phaseGraph, and phaseInstancePrefabs | Parent/child interface names such as In1, Out1; CutDestination | Lab 4 |
SpawnManager / questSpawnManagerNodeDefinition | Issue community/spawner actions | actions[]; Lab 5 uses questCommunityTemplate_NodeType | In, Out, CutDestination | Lab 5 |
Checkpoint / questCheckpointNodeDefinition | Establish an explicit quest checkpoint in the supplied lifecycle | Checkpoint flags and containing graph position | In, Out, CutDestination | Lab 5 |
Scene / questSceneNodeDefinition | Start an archived scene at a world marker and receive named results | sceneFile, sceneLocation, interruption fields | Named entry inputs and exit outputs; interruption/return outputs; CutDestination | Lab 5 |
Input and output have two socket layers
In an external child, the parent interface and the child’s internal flow are different layers:
parent phase node In1
-> child input socketName In1
-> child input node Out
child work
-> child output node In
-> child output socketName Out1
-> parent phase node Out1
In1 and Out1 are CName interface values in this arrangement. They are not
universal requirements for every phase node. Observed in vanilla, a
focused external phase node also exists with In1 but no Out1. See Inputs
and outputs.
Journal and mappin state comes from the incoming socket
For questJournalNodeDefinition, the same path can receive different
operations:
| Input | Requested journal state |
|---|---|
Active | Activate the target entry |
Inactive | Inactivate it |
Succeeded | Mark it succeeded |
Failed | Mark it failed |
For the quest-pin form of questMappinManagerNodeDefinition, the focused
inputs are Active and Inactive. disablePreviousMappins is transition
policy; it does not replace the state socket. In either node, Out proves
only that graph execution continued after the request, not that the UI was
visibly redrawn.
Quest control-flow nodes
Graph-level logical nodes combine execution signals. They do not replace a
questLogicalCondition tree inside Condition or Pause Condition.
| RED type | Structural role | Socket/property shape | Evidence boundary |
|---|---|---|---|
questLogicalAndNodeDefinition | Join-shaped convergence | inputSocketCount, outputSocketCount, numbered ordinary sockets | Observed in vanilla: 2–11 inputs and one output in the retained corpus; arrival memory and save restoration are not established by the type name |
questLogicalXorNodeDefinition | Race-shaped convergence | inputSocketCount, outputSocketCount, numbered ordinary sockets | Lab 2’s exact two-input/one-output shape is Structurally validated; automatic loser cancellation is not implied |
questLogicalHubNodeDefinition | General structural routing | Counts plus ordinary input/output sockets | Observed in vanilla: split, merge, and 1→1 shapes all occur; “fan-out node” is not a complete definition |
questSwitchNodeDefinition | Map conditions to case sockets | conditions[], each socketId; Case<socketId> and Otherwise outputs | Observed in vanilla for one retained switch; ordering and Otherwise runtime policy remain Experimental |
questCutControlNodeDefinition | Emit an explicit cut toward named targets | ordinary In/Out plus CutSource | CutSource→CutDestination edges are Observed in vanilla; rollback, propagation, persistence, and re-arm behavior are not inferred |
Use Signal flow: joins, races, hubs, and switches for the retained counts and Parallel monitors and cancellation before wiring cuts.
Other quest node families introduced by the book
| RED type | Job | Decisive payload | Evidence route |
|---|---|---|---|
questRewardManagerNodeDefinition | Grant one or more reward records | questGiveReward_NodeType.rewards[] | The focused arrangement is covered in Rewards and completion; it is not a Labs 1–5 dependency |
questPuppetAIManagerNodeDefinition | Request a puppet AI tier or operation | Target entity reference and operation-specific fields | Lab 5 wraps one Cinematic-tier request inside scnQuestNode; see Entry, exit, and quest handoff |
questMiscAICommandNode | Assign or clear a temporary scripted AI role | Typed command params, target actor, follow/path/role data | AI roles, behavior, and workspots |
questUseWorkspotNodeDefinition | Put a selected actor into a world or scene-local workspot contract | Actor reference plus versioned workspot params/instance ID | Animation events and workspots |
questInteractiveObjectManagerNodeDefinition | Issue a typed operation to an interactive object/device | questDeviceManager_NodeType, controller/action names, target and action properties | Advanced devices and interactions |
These manager nodes perform their own operation only. A reward node does not set a completion fact or succeed the journal, and a PuppetAI request does not finish a scene.
Socket vocabulary
| Socket name or type | Local meaning | Common mistake |
|---|---|---|
In / Out | Ordinary internal entry and continuation | Treating Out as proof that an external system visibly completed |
In1 / Out1 | First named phase interface or numbered logical socket, depending on node type | Assuming the text has one global meaning or that every phase exposes both |
True / False | Results of an immediate Condition node | Expecting the False route to wait and later become True |
Active, Inactive, Succeeded, Failed | State-operation inputs on journal-like manager nodes | Looking only at the node payload and ignoring which input the edge reaches |
Case<n> / Otherwise | Switch outputs mapped from condition items | Assuming case evaluation order from array order |
CutSource | Explicit cancellation source on a Cut Control | Wiring it as ordinary success flow |
CutDestination | Target for a cut edge | Assuming an unwired destination provides automatic cleanup |
Scene entry name, such as start | Input on questSceneNodeDefinition matching a scene entryPoint | Using the scene Start node’s numeric ID as the caller interface |
Scene exit name, such as contact_done | Output on questSceneNodeDefinition matching a scene exitPoint | Inventing an end output because the scene contains an End node |
<scenario> INT / <scenario> RET | Quest-scene outputs associated with scene interruption and return policy | Treating them as normal completion without a tested interruption design |
The socket type remains decisive. A socket named CutDestination has the
cut-destination role; it is not an ordinary input merely because another
record also contains the text In.
Lab 5 scene-caller socket contract
The completed First Contact child uses this exact
questSceneNodeDefinition interface:
| Socket | Type | Connection |
|---|---|---|
CutDestination | CutDestination | Unconnected |
start | Input | From the child route after community readiness and broad setup |
contact_done | Output | To the meet-success and cleanup route |
Default INT | Output | Unconnected |
Default RET | Output | Unconnected |
The resource shape is Structurally validated. Active-line interruption, return, and cut behavior remain Experimental outside Lab 5’s frozen acceptance cases.
Scene graph node index
Scene nodes use scnNodeId and scene socket stamps. They do not use
questSocketDefinition directly, except when scnQuestNode wraps a quest
node.
| Scene RED type | Job | Boundary data |
|---|---|---|
scnStartNode | Receive the published start entry and fan one 0/0 output to two destinations | Listed in sceneGraph.startNodes; targeted by entryPoints[].nodeId |
scnSectionNode | Own actor behavior, timed line events, duration, and normal/cancel outputs | Normal 0/0 reaches End; cancel 1/0 is unconnected in the fixture |
scnQuestNode | Wrap a scene-local quest node; First Contact wraps questPuppetAIManagerNodeDefinition | Input/output mappings connect scene stamps to the wrapped quest sockets |
scnEndNode | Terminate the normal scene route | Listed in sceneGraph.endNodes; targeted by the named contact_done exit |
scnChoiceNode | Present scene-local screenplay options and route the selected branch | Option-to-screenplay IDs, choice conditions, output ordinals, and later named exits are separate joins |
scnRewindableSectionNode | Own a seekable/rewindable timed-event collection | Playback strategies, duration, RID/clue/camera events, and side-effect reconstruction policy |
The exact scene topology is:
+-> Section 2 -> End 3 [contact_done]
Start 1 -- one fan-out --+
+-> scnQuestNode 4 / PuppetAI [fire-and-forget]
The PuppetAI branch has no output destination and does not join the line branch. The section alone reaches End. See Entry, exit, and quest handoff.
Scene socket stamps are name/ordinal pairs
The First Contact scene supplies this exact stamp table:
| Source | Output stamp | Destination | Input stamp |
|---|---|---|---|
Start 1 | name 0, ordinal 0 | Section 2 | name 0, ordinal 0 |
Start 1 | name 0, ordinal 0 | PuppetAI wrapper 4 | name 0, ordinal 1 |
Section 2 normal | name 0, ordinal 0 | End 3 | name 0, ordinal 0 |
The two Start connections are one output fan-out, not two differently stamped
outputs. The wrapper’s input ordinal 1 selects its mapped internal In; its
ordinal 0 is reserved for the CutDestination mapping. Treat ordinals as a
node-local contract, not whole-scene execution order.
Review checklist
Before diagnosing a graph from its labels:
- Name the containing questphase or scene graph as well as the node ID.
- Confirm the concrete RED type and every non-null typed payload handle.
- Trace each edge by source socket and destination socket.
- Distinguish one socket with several connections from several sockets.
- Distinguish a quest socket from a scene socket stamp.
- Confirm every phase input/output and scene entry/exit name on both sides.
- Inventory parallel waits, delays, scenes, community operations, and cut targets before permitting termination.
- Apply the evidence label of the exact arrangement, not the reputation of the node name.
Condition index
Find a native condition class, its wrapper, and the chapter explaining its fields. Test listener timing and save/load behavior when adapting a condition to your quest.
The condition inventory is Observed in vanilla. Use the named depot paths to inspect matching nodes in your own installation. For a worked example, start with Labs at a glance.
Choose the evaluation schedule first
The same predicate can sit beneath two different graph schedules:
| Graph node | Question answered on entry | Focused ordinary results |
|---|---|---|
questConditionNodeDefinition | “Is this predicate fulfilled now?” | True or False |
questPauseConditionNodeDefinition | “When this predicate becomes fulfilled, continue this active route.” | Out after fulfillment |
An immediate Condition does not wait on its False route. A Pause Condition
does not expose an ordinary False continuation in the focused shape. See
Immediate branches and waiting gates.
Read the payload layers
Many condition families use two object layers:
Condition or Pause Condition node
└── condition domain wrapper
└── type concrete predicate payload
For example:
questFactsDBCondition
└── questVarComparison_ConditionType
Other wrappers, including questTriggerCondition, carry decisive fields
directly. Preserve the actual wrapper/payload pairing; replacing a wrapper
while leaving an incompatible nested handle is not a type conversion.
Families reduced to practical property contracts
| Family | Wrapper | Concrete payload or direct fields | Decisive values | Book evidence |
|---|---|---|---|---|
| Fact comparison | questFactsDBCondition | questVarComparison_ConditionType | factName, signed value, comparisonType | 259 retained instances are Observed in vanilla; exact Lab 1–5 fact predicates are Structurally validated |
| Trigger | questTriggerCondition | Direct type, triggerAreaRef, activatorRef, isPlayerActivator | IsInside, IsOutside, or another specifically evidenced predicate; exact NodeRef and activator | 214 retained instances are Observed in vanilla; exact Lab 3–5 player gates are Structurally validated |
| Distance | questDistanceCondition | questDistanceComparison_ConditionType containing operand handles | questObjectDistance, questValueDistance, comparator, both object identities | 55 retained instances are Observed in vanilla; no Lab 1–5 distance gate |
| Journal lifecycle | questJournalCondition | questJournalEntryState_ConditionType | Typed path, engine state, payload-specific inverted | 66 retained instances are Observed in vanilla |
| Journal user state | questJournalCondition | questJournalEntry_ConditionType | Typed path and user-facing state | 21 retained instances are Observed in vanilla |
| Journal visited | questJournalCondition | questJournalEntryVisited_ConditionType | Typed path and visited | One retained instance is Observed in vanilla |
| Realtime delay | questTimeCondition | questRealtimeDelay_ConditionType | hours, minutes, seconds, native spelling miliseconds | 146 retained instances are Observed in vanilla; exact Lab 1, 2, and 4 delays are Structurally validated |
| Game-time delay | questTimeCondition | questGameTimeDelay_ConditionType | days, hours, minutes, seconds | 19 retained instances are Observed in vanilla |
| Character spawned | questCharacterCondition | questCharacterSpawned_ConditionType | objectRef, comparison type/count, entireCommunity | 73 retained instances are Observed in vanilla; exact Lab 5 Greater 0 community-scoped wait is Structurally validated |
The focused authoring fields and validation steps are in Condition payloads. Community readiness adds the identity and activation-order requirements in Activation, readiness, and acquisition.
Fact comparators
The property guide exposes these native values. The count column keeps the retained corpus observation separate from enum availability:
comparisonType | Predicate | Retained instances |
|---|---|---|
Equal | fact equals authored value | 55 |
NotEqual | fact does not equal authored value | 2 |
Greater | fact is greater than authored value | 138 |
GreaterOrEqual | fact is at least authored value | 55 |
Less | fact is less than authored value | 9 |
LessOrEqual | fact is at most authored value | 0 |
The absence of LessOrEqual from this focused sample is not evidence that the
enum cannot represent it.
Facts are signed integers. Equal 1 and Greater 0 are not interchangeable
when another writer can store 2.
Trigger predicates
The retained trigger inventory contains:
Direct questTriggerCondition.type | Retained instances |
|---|---|
IsInside | 153 |
IsOutside | 56 |
Entered | 5 |
These names are search keys, not proof of exact state-versus-edge behavior
through activation, teleportation, streaming, or save/load. The exact Lab 3–5
resources use state-shaped IsInside / IsOutside forms with the player form
of the activator fields; test the intended crossing and reload behavior in game.
Time payloads
Concrete payload beneath questTimeCondition | Retained instances | Current depth |
|---|---|---|
questRealtimeDelay_ConditionType | 146 | Property recipe and Structurally validated lab examples |
questGameTimeDelay_ConditionType | 19 | Property recipe plus vanilla observations |
questTimePeriod_ConditionType | 2 | Inventory only |
questTickDelay_ConditionType | 1 | Inventory only |
The payload name does not settle pause-menu, time-skip, fast-travel, loading-screen, process-restart, or save-restoration policy. Those are separate runtime cases; see Delays, facts, and persistence boundaries.
Logical condition trees
questLogicalCondition combines child predicates inside one Condition or
Pause Condition. It is not a graph-level join or race node.
Direct operation | Retained instances | Safe current statement |
|---|---|---|
AND | 72 | Every represented child predicate is required by the authored tree; exact evaluation order and short-circuit policy are not inferred |
OR | 14 | The tree represents alternatives but does not report which child supplied fulfillment |
XOR | 1 | The four-child serialized shape exists; its multi-true truth policy remains Experimental |
The exact nested AND condition in Lab 2 is Structurally validated. There is
no generic NOT wrapper in the retained corpus. Use a payload’s own negative
form only when that payload owns one—for example NotEqual, IsOutside, or
the journal-state payload’s inverted field. See Boolean condition
trees.
Do not confuse the table with questLogicalAndNodeDefinition or
questLogicalXorNodeDefinition. Those graph nodes combine execution signals
and have sockets; questLogicalCondition combines predicate objects and has
no listener sockets to cancel.
Object-condition families
The retained corpus contains 60 questObjectCondition wrappers. Their nested
payloads account for all 60 definitions:
| Concrete payload | Retained instances | External owner to identify before authoring |
|---|---|---|
questDevice_ConditionType | 44 | Placed device identity, template/controller state, and the producer of the observed device state |
questInventory_ConditionType | 11 | Item/record identity, inventory owner, quantity or state, and the operation that changes it |
questScan_ConditionType | 3 | Scannable target identity and scan event/state owner |
questDestruction_ConditionType | 2 | Target identity and destruction-state producer |
All four rows are Observed in vanilla inventory. The device row now has a bounded property/ownership procedure in Advanced devices and interactions, while a new custom-device lifecycle remains Experimental until its exact matrix passes. The other rows remain discovery entries rather than complete property recipes. The shared wrapper does not imply shared fields or runtime behavior.
Character, combat, and workspot families
The 250 retained questCharacterCondition wrappers contain:
| Concrete payload | Retained instances |
|---|---|
questCharacterSpawned_ConditionType | 73 |
questCharacterMount_ConditionType | 67 |
questCharacterKilled_ConditionType | 37 |
questCharacterStatusEffect_CondtionType | 29 |
questCharacterState_ConditionType | 18 |
questCharacterCombat_ConditionType | 11 |
questCharacterHealth_ConditionType | 4 |
questCharacterHit_ConditionType | 4 |
questCharacterWorkspot_ConditionType | 4 |
questCharacterAttack_ConditionType | 1 |
questCharacterGender_CondtionType | 1 |
questCharacterQuickHacked_ConditionType | 1 |
Condtion is the native spelling in the two class names above. Preserve it
when searching. Except for the bounded CharacterSpawned contract taught by
Lab 5, these rows remain Observed in vanilla discovery entries. Before
authoring one, establish actor/community ownership, reference form,
streaming/readiness, the producer of the state change, and cleanup.
Lab 5’s focused spawn wait is:
questCharacterCondition
└── questCharacterSpawned_ConditionType
├── objectRef.reference: community NodeRef
└── comparisonParams
├── comparisonType: Greater
├── count: 0
└── entireCommunity: 1
Read it literally: the community-scoped spawned count is greater than zero. It does not prove that every actor in a multi-entry community is ready.
Scene, content, phone, and system families
| Wrapper | Concrete payload | Retained instances | Boundary |
|---|---|---|---|
questSceneCondition | questSceneNode_ConditionType | 31 | Scene state consumer; it does not launch the scene |
questContentCondition | questContentLock_ConditionType | 10 | Content-lock predicate inventory |
questContentCondition | questContentToken_ConditionType | 5 | Content-token predicate inventory |
questSystemCondition | questPhoneMuted_ConditionType | 26 | Phone-muted predicate inventory |
questSystemCondition | questCameraFocus_ConditionType | 24 | Camera-focus predicate inventory |
questSystemCondition | questPhone_ConditionType | 4 | Phone-state predicate inventory |
Every row is Observed in vanilla. A condition is a consumer of state; it does not create the scene, token, phone call, or camera behavior it observes.
Vehicle and spawning families
| Wrapper | Concrete payload | Retained instances | Boundary |
|---|---|---|---|
questVehicleCondition | questVehicleSpeed_ConditionType | 3 | Identify the exact vehicle, load/spawn owner, and speed comparison |
questVehicleCondition | questVehicleTrunk_ConditionType | 2 | Identify vehicle/trunk state and the producer of that state |
questVehicleCondition | questVehicleWater_ConditionType | 1 | Inventory only |
questSpawnerCondition | questSpawnerReady_ConditionType | 7 | Identify spawner ownership, activation order, failure route, and cleanup |
These rare families are Observed in vanilla and remain poor copy-by-name recipes. Test them in a small mod-owned project before integrating them into a quest.
Distance operands
All 55 retained questDistanceCondition wrappers use
questDistanceComparison_ConditionType. The focused operand relationship is:
questDistanceComparison_ConditionType
├── distanceDefinition1 -> questObjectDistance
│ ├── entityRef
│ └── nodeRef2
├── distanceDefinition2 -> questValueDistance
│ └── distanceValue
└── comparisonType
Call distanceValue a numeric threshold. The retained shape does not prove a
universal measurement unit, the reference points used on each entity, or
streaming/teleportation policy.
Focused vanilla search routes
Extract these resources from your own game and retain only focused notes or excerpts. Do not redistribute the CR2W files or full serializations.
| Families | Depot path |
|---|---|
| Facts, logical trees, triggers, time, journal, content | base\open_world\minor_activities\watson\northside\ma_wat_nid_15\ma_wat_nid_15_phase.questphase |
| Game-time/realtime, journal, content token, phone | base\quest\side_quests\sq011\phases\sq011_concert.questphase |
| Inventory, scan, scene, character, vehicle trunk | base\open_world\street_stories\heywood\vista_del_rey\sts_hey_rey_09\phases\sts_hey_rey_09_openworld.questphase |
| Device, killed, spawned, workspot, quickhack | base\open_world\street_stories\watson\little_china\sts_wat_lch_01\phase\sts_wat_lch_01_combat.questphase |
| Destruction, device, combat, vehicle speed | base\open_world\street_stories\watson\northside_industrial_district\sts_wat_nid_03\phases\sts_wat_nid_03_openworld.questphase |
| Spawner ready, distance, character mount | base\open_world\street_stories\westbrook\japantown\sts_wbr_jpn_09\phases\sts_wbr_jpn_09_gameplay.questphase |
Before using an indexed family
For a condition not yet taught as a practical recipe, retain:
- the exact immediate or waiting graph node and socket route;
- the wrapper, nested concrete payload, and every decisive field;
- ownership and resolution for every external identifier;
- the system or graph operation that produces the observed state;
- a mod-owned WolvenKit
8.19.0round trip with non-null compatible handles; - already-true, normal-transition, false/failure, save/load, stream-loss, cancellation, completion, and re-entry cases as applicable;
- exact versions, installed filenames, save history, and logs.
Until those checks exist, a corpus row remains Observed in vanilla and a new behavior claim remains Experimental.
Identifier-domain reference
Values that look alike in WolvenKit are not interchangeable. Use this page to name the type, owner, and scope of an identifier before comparing, copying, or regenerating it.
The exact Lab 1–5 joins cited here are Structurally validated. The broader world and scene relationships inherit the evidence labels of their linked chapters. This page adds no runtime claim.
Quick domain map
| Domain | Native type or serialized shape | Scope / owner | Example | Never substitute |
|---|---|---|---|---|
| Depot address | ResourcePath or a registration path value | Game virtual depot | mod\cqa\cqa005\scenes\cqa005_first_contact.scene | Windows path, NodeRef, journal path |
| CR2W object identity | HandleId / HandleRefId | One serialized CR2W object graph | "HandleId": "15" | Graph node ID, scene ID, world identity |
| Quest graph node | integer node id | One questGraphDefinition | Lab 1 FactsDB node 15 | HandleId, scene node ID |
| Quest socket/interface name | CName | One node or parent/child interface | In, Out, In1, Out1, contact_done | Numeric node ID or ordinal |
| Scene graph node | scnNodeId | One scnSceneGraph | First Contact Section 2 | Quest node ID or performer ID |
| Scene socket stamp | name plus ordinal | One scene-node socket mapping | output 0/0, input 0/1 | Quest questSocketDefinition |
| World reference | NodeRef | Registered world / quest-prefab hierarchy | #cqa005_com_contact | CR2W handle, ResourcePath, TweakDBID |
| Gameplay record | TweakDBID | TweakDB | Character.jpn_tyger_claws_biker2_ranged2_copperhead_wa | .ent path, NodeRef |
| Name token | CName | Field-specific RED name domain | community entry contact | Fact, localization key, ResourcePath |
| Fact | factName string with signed integer value | Facts database and save | cqa005_completed | CName, journal ID, Boolean field |
| Journal lookup | gameJournalPath | Merged journal tree | quests/minor_quest/cqa005/... | Depot path, localization key |
| Journal component | entry id | Parent entry’s child collection | cqa005_01_obj_meet | fileEntryIndex, graph ID |
| UI localization | textual secondary key | Merged onscreen localization table | cqa_cqa001_title | Spoken-line RUID |
| Spoken/choice localization | scnlocLocstringId.ruid | Scene localization lookup | 9638591835734011695 | Screenplay item ID, event ID |
| Scene actor | scnActorId | One scene | contact 0, V 1 | Performer ID, community ID |
| Scene performer symbol | scnPerformerId | Scene debug-symbol mapping | contact 1, V 257 | Actor ID |
| Screenplay item | scnscreenplayItemId | One screenplay store | First Contact line 1 | Locstring or event ID |
| Timed scene event | scnSceneEventId | One scene event collection | 8646165628675208917 | Locstring or screenplay item ID |
| Lipsync resource slot | scnLipsyncAnimSetSRRefId | Index into one scene’s lipsync array | slot 0 | Actor or performer ID |
Resource addresses
Filesystem paths and depot paths
These addresses can point toward the same source file while belonging to different domains:
H:\MyProject\source\archive\mod\myquest\phase.questphase
filesystem path
mod\myquest\phase.questphase depot path / ResourcePath value
Only the second form belongs in CR2W resource references and ArchiveXL path
values. Preserve the backslashes and spelling shown by WolvenKit. A file below
source\raw can end in .questphase.json; that conversion filename is not the
runtime depot path.
Registration path versus soft reference
The same depot path can be consumed by different owners:
| Use | Owner | Meaning |
|---|---|---|
Root quest.phases[].path | ArchiveXL | Attach a mod-owned root beneath the game quest root |
phaseResource.DepotPath | questPhaseNodeDefinition | Resolve an archived external child |
sceneFile.DepotPath | questSceneNodeDefinition | Resolve an archived scene |
Descriptor data.DepotPath | worldStreamingSectorDescriptor | Resolve a streaming sector |
A soft ResourcePath still requires the target file. It does not perform
ArchiveXL root registration.
Serialized object and graph identity
CR2W handles
{
"HandleId": "15",
"Data": {
"$type": "questFactsDBManagerNodeDefinition",
"id": 15
}
}
HandleId identifies the handled object in this serialization. The node’s
integer id identifies it in the quest graph. They happen to match in the
example and are not required to match.
Rules:
- every
HandleRefIdmust resolve to one compatible handled object; - handle identity is local to one CR2W object graph;
- WolvenKit can normalize handle allocation during a semantic round trip;
- never repair a graph by forcing handle numbers to equal node numbers.
Graph-local IDs
A node citation is incomplete without its graph scope:
cqa005.questphase, quest node 12
cqa005_contact.questphase, quest node 12
cqa005_first_contact.scene, scene node 2
The repeated number does not join those nodes. Quest nodes use integer id;
scene graph nodes use typed scnNodeId. Parent graphs, child graphs, and scene
graphs can all reuse a number.
Socket names and ordinals
Quest socket names are normally CNames such as Active, True, Out1, or
contact_done. Scene socket stamps use a name/ordinal pair. Neither is a graph
node ID.
An ordinal is local to the node type. First Contact’s Start output 0/0
targets both Section input 0/0 and PuppetAI-wrapper input 0/1; that does
not mean the wrapper runs “second” in the whole scene.
World and quest-prefab identity
Local and full NodeRefs
The quest side can use a local child beneath a declared prefab root:
#cqa005_tr_setup
The world side registers the full child:
$/mod/cqa/cqa005/#cqa005_pr_first_contact/#cqa005_tr_setup
The root likewise appears in local form in a questphase phasePrefabs entry
and full form on the Quest-sector descriptor. Compare each representation in
its owning field; the prefix difference is expected.
The field named QuestPrefabRefHash is exposed as a NodeRef string in the
retained WolvenKit JSON. Do not replace it with a numeric hash merely because
its property name contains “Hash.”
NodeRef is not ResourcePath
| Value | Type | Resolves |
|---|---|---|
#cqa005_com_contact | NodeRef | A world object beneath an available prefab root |
mod\cqa\cqa005\scenes\cqa005_first_contact.scene | ResourcePath | A CR2W resource in the depot |
Character.jpn_tyger_claws_biker2_ranged2_copperhead_wa | TweakDBID | A gameplay record |
All three can be printed as text. Their lookup systems are independent.
Community/source, registry, and AI-spot IDs
The compact Lab 5 community places several unsigned 64-bit world identities beside one another:
| Domain | Canonical Lab 5 value | Derived from |
|---|---|---|
| Community/source identity | 5948510988927765319 | Full community NodeRef |
| Registry-node identity | 6908684691797323855 | Full community NodeRef with _registry appended to the final community name |
| AI-spot global identity | 15950783814303760596 | Full AI-spot NodeRef |
The source identity intentionally joins:
worldCompiledCommunityAreaNode_Streamable.sourceObjectId.hash
== worldCommunityRegistryItem.communityId.entityId.hash
The registry-node identity and every AI-spot identity must remain separate.
The spot identity then joins the registry persistent row and the compiled
area’s spotNodeIds entry.
These numbers use RED4’s alias-aware NodeRef operation. The # marker is
hierarchy/alias syntax, not an ordinary byte to pass through a generic text
hash. Do not derive these IDs with plain FNV over a debug label or printed
path. Retain the canonical full NodeRef beside every numeric value and reject
zero or unintended collisions. See Registries and compiled
areas.
Community entry and phase names
contact and default in Lab 5 are community-local CNames:
entryName: contact
phaseName: default
They must match across registry template, compiled-area mirror, activation
action, readiness/acquisition references, and scene actor parameters where
applicable. They are not actor display names, TweakDB records, NodeRefs, or
world-global IDs. None in a community action is an authored CName token, not
JSON null or an omitted field.
Journal, facts, and localization
Fact name and value
A fact consists of a name plus a signed integer value:
cqa005_completed = 1
The suffix _completed is an author convention. The engine learns its meaning
only from graph reads and writes. A fact is not automatically reset when a
questphase terminates or an archive changes.
Journal path fields
gameJournalPath has four distinct fields:
| Field | Domain |
|---|---|
realPath | Slash-separated journal-entry IDs |
className | CName for the intended leaf entry type |
fileEntryIndex | Zero-based component position of the containing gameJournalFileEntry |
editorPath | Editor-facing text; the tutorial-owned paths leave it empty |
For this path:
quests/minor_quest/cqa001/cqa001_01/cqa001_01_obj_wait
the containing gameJournalQuest is component 2, so fileEntryIndex is
2. The value is not the objective’s child index, graph node ID, or CR2W
handle. See Journal trees and typed paths.
Three localization domains
| Content | Identifier | Owner |
|---|---|---|
| Journal/UI text | Textual LocalizationString.value matching onscreen secondaryKey | Registered onscreen localization resource |
| Spoken scene line | Unsigned scnlocLocstringId.ruid matching subtitle and VO stringId | External subtitle-map/entries and VO-map/WEM branches |
| Scene choice | Unsigned locstring RUID reached through a screenplay option | Scene’s embedded scnlocLocStoreEmbedded |
A spoken line’s RUID cannot be replaced with its screenplay item ID. A scene choice cannot be repaired by adding its ID to the onscreen localization table. See Localization paths.
Scene identifier domains
First Contact deliberately places several small and large numbers beside one another:
| Typed domain | Values | Identifies |
|---|---|---|
scnActorId | 0, 1 | Contact and V actor definitions |
scnPerformerId | 1, 257 | Debug performer-symbol rows |
scnNodeId | 1, 2, 3, 4 | Start, Section, End, and PuppetAI wrapper |
scnscreenplayItemId | 1 | The screenplay line and the event-to-line join |
scnSceneEventId | 8646165628675208917 | The timed line event instance |
scnlocLocstringId | 9638591835734011695 | External subtitle and voice lookup |
scnLipsyncAnimSetSRRefId | 0 | Slot in resouresReferences.lipsyncAnimSets |
scnInterruptionScenarioId | 0 | The scene’s Default interruption scenario |
Repeated 0 or 1 values do not create a cross-domain join.
Actor versus performer
The exact debug-symbol rows are:
| Role | Actor ID | Performer ID |
|---|---|---|
| Contact | 0 | 1 |
| V | 1 | 257 |
This fixture preserves the observed actor ID * 256 + 1 performer-symbol
relationship. That does not authorize using performer 257 in a screenplay
field that expects scnActorId(1).
Screenplay item, event, and locstring
The join is directional:
scnDialogLineEvent.screenplayLineId = screenplay item 1
-> scnscreenplayDialogLine.itemId = screenplay item 1
-> scnscreenplayDialogLine.locstringId = 9638591835734011695
Event ID 8646165628675208917 identifies the scheduled event itself. Do not
derive it from the locstring, reuse the item ID, or truncate either unsigned
value to 32 bits.
Lipsync slot
Both Lab 5 actors refer to scnLipsyncAnimSetSRRefId(0), and the scene owns
one addressable row at array slot 0. The ID is an array index, not an actor
or performer identity. Actor 1 referring to slot 1 would be out of range
in that exact resource.
Embedded choice variants
For later choice scenes, keep these domains distinct:
| Field | Role |
|---|---|
scnChoiceNodeOption.screenplayOptionId | Join from graph option to screenplay option |
scnscreenplayChoiceOption.itemId | Screenplay option identity |
scnscreenplayChoiceOption.locstringId | Choice-text lookup identity |
Descriptor variantId.ruid | Join to the matching payload variant |
Descriptor vpeIndex | Zero-based position in vpEntries[] |
Moving a payload does not update vpeIndex by magic. Preserve descriptor,
variant, and payload identity together.
Unsigned numeric handling
Scene RUIDs and several world identities exceed signed 32-bit range. Preserve them as unsigned 64-bit values and print them as exact decimal strings when a tool’s number model cannot represent all 64 bits safely.
Do not:
- truncate to 32 bits;
- round through a floating-point number;
- sort decimal spellings lexicographically when numeric order matters;
- reuse an ID because it is small and currently unused in another domain;
- hash a NodeRef with a generic text algorithm when RED4 alias semantics are required.
Domain-audit checklist
For every identifier in a practical resource:
- Record the native type and full property path.
- Record the owner and scope: CR2W graph, quest graph, scene, journal tree, world hierarchy, TweakDB, localization table, or save.
- Record the canonical source string for derived world IDs.
- Verify every join on both sides in the mod-owned resources.
- Reject null handles, zero values where identity is required, out-of-range indices, and unintended collisions.
- Reopen and round-trip the resources before comparing semantics.
- Test external resolution and save behavior separately from structural validity.
Journal path reference
A gameJournalPath addresses one typed entry inside the merged journal tree.
It does not address the .journal CR2W file, a handle inside that file, or a
localization string. Use this page when filling a quest node’s path value;
use Journal trees and typed paths for the full
authoring model.
| Record | Value |
|---|---|
| Practical baseline | Cyberpunk 2077 2.31a, WolvenKit 8.19.0, ArchiveXL 1.27.0, RED4ext 1.30.0, redscript 0.5.31 |
| Lab 1–5 path resources | Structurally validated |
| In-game state transitions | Verify with the relevant lab’s save and reload cases |
Journal activation, visited state, tracking, success, and failure can persist in a save. Use an untouched save made before the candidate journal was first installed when changing a tree, path, or entry identity.
The four fields
gameJournalPath field | RED value kind | What to enter |
|---|---|---|
realPath | String | Slash-separated entry id values, excluding the anonymous gameJournalRootFolderEntry |
className | CName | Exact RED class of the intended target in tutorial-owned trees |
fileEntryIndex | Integer | Zero-based realPath component containing the nearest gameJournalFileEntry |
editorPath | String | Empty string in Labs 1–5 |
For Lab 1’s objective:
realPath: quests/minor_quest/cqa001/cqa001_01/cqa001_01_obj_wait
className: gameJournalQuestObjective
fileEntryIndex: 2
editorPath: ""
Do not put a depot path in realPath. These three addresses are distinct:
mod\cqa\cqa001\journal\cqa001.journal cooked depot resource
quests/minor_quest/cqa001/... merged journal entry
cqa_cqa001_objective_wait localization key
Calculate fileEntryIndex
Split realPath at /, count from zero, and locate the component whose entry
is the nearest containing subtype of gameJournalFileEntry.
0 quests
1 minor_quest
2 cqa001 gameJournalQuest; containing file entry
3 cqa001_01
4 cqa001_01_obj_wait
The value is therefore 2 for the quest, phase, objectives, and nested quest
mappin in every quests/minor_quest/cqa00N/... tutorial tree. It is not the
leaf depth and is not a constant for every journal family.
| Path family | Typical containing file entry | Example index |
|---|---|---|
quests/minor_quest/<quest>/... | gameJournalQuest at component 2 | 2 |
contacts/<contact>/... | gameJournalContact at component 1 | 1 |
points_of_interest/minor_quests/... | gameJournalPointOfInterestGroup at component 1 | 1 |
onscreens/emails/quests/minor_quest/<quest>/files/... | gameJournalFileGroup at component 5 | 5 |
onscreens/emails/quests/minor_quest/<quest>/messages/... | gameJournalOnscreenGroup at component 5 | 5 |
Calculate the value from the actual contributed tree. A different folder layout can move the containing file entry.
Common target classes
| Target | className |
|---|---|
| Quest | gameJournalQuest |
| Quest phase | gameJournalQuestPhase |
| Quest objective | gameJournalQuestObjective |
| Objective description | gameJournalQuestDescription |
| Quest mappin | gameJournalQuestMapPin |
| Contact | gameJournalContact |
| Phone message | gameJournalPhoneMessage |
| Phone choice group | gameJournalPhoneChoiceGroup |
| Phone choice | gameJournalPhoneChoiceEntry |
Use the exact target type for mod-owned entries. Observed in vanilla: some
older quest nodes carry surprising or less-specific className values. When
researching one, resolve realPath against the extracted journal tree before
deciding whether the value is intentional. Do not copy a legacy mismatch into
a new resource.
Lab 1–5 path sheet
Every path below uses fileEntryIndex: 2 and editorPath: "". The class is
gameJournalQuest, gameJournalQuestPhase, gameJournalQuestObjective, or
gameJournalQuestMapPin according to the last component shown.
| Lab | Entry | realPath |
|---|---|---|
| First Signal | Quest | quests/minor_quest/cqa001 |
| First Signal | Phase | quests/minor_quest/cqa001/cqa001_01 |
| First Signal | Wait objective | quests/minor_quest/cqa001/cqa001_01/cqa001_01_obj_wait |
| Signal Race | Quest | quests/minor_quest/cqa002 |
| Signal Race | Phase | quests/minor_quest/cqa002/cqa002_01 |
| Signal Race | Wait objective | quests/minor_quest/cqa002/cqa002_01/cqa002_01_obj_wait |
| Signal Race | Stable objective | quests/minor_quest/cqa002/cqa002_01/cqa002_01_obj_stable |
| Boundary Check | Quest | quests/minor_quest/cqa003 |
| Boundary Check | Phase | quests/minor_quest/cqa003/cqa003_01 |
| Boundary Check | Reach objective | quests/minor_quest/cqa003/cqa003_01/cqa003_01_obj_reach |
| Boundary Check | Quest mappin | quests/minor_quest/cqa003/cqa003_01/cqa003_01_obj_reach/cqa003_01_qmp_checkpoint |
| Boundary Check | Leave objective | quests/minor_quest/cqa003/cqa003_01/cqa003_01_obj_leave |
| Handoff Point | Quest | quests/minor_quest/cqa004 |
| Handoff Point | Phase | quests/minor_quest/cqa004/cqa004_01 |
| Handoff Point | Reach objective | quests/minor_quest/cqa004/cqa004_01/cqa004_01_obj_reach |
| Handoff Point | Quest mappin | quests/minor_quest/cqa004/cqa004_01/cqa004_01_obj_reach/cqa004_01_qmp_handoff |
| Handoff Point | Leave objective | quests/minor_quest/cqa004/cqa004_01/cqa004_01_obj_leave |
| Handoff Point | Confirmation objective | quests/minor_quest/cqa004/cqa004_01/cqa004_01_obj_confirm |
| First Contact | Quest | quests/minor_quest/cqa005 |
| First Contact | Phase | quests/minor_quest/cqa005/cqa005_01 |
| First Contact | Meet objective | quests/minor_quest/cqa005/cqa005_01/cqa005_01_obj_meet |
| First Contact | Quest mappin | quests/minor_quest/cqa005/cqa005_01/cqa005_01_obj_meet/cqa005_01_qmp_contact |
| First Contact | Leave objective | quests/minor_quest/cqa005/cqa005_01/cqa005_01_obj_leave |
These values are Structurally validated against the supplied mod-owned journal CR2Ws and questphases. A matching string and successful CR2W round trip do not prove that a saved journal transition will replay in game.
Registration and resolution check
The journal contribution must be registered independently of every path that targets it:
journal:
- mod\cqa\cqa001\journal\cqa001.journal
Check the chain in this order:
- The cooked
.journalexists beneathsource\archiveat the intended depot path. - The loose
.archive.xlregisters that exact depot path. - The journal’s entry
idchain producesrealPathexactly, including case and separators. classNamematches the mod-owned target type.fileEntryIndexidentifies the nearest containing file entry.- The localization key on the target resolves through the separate onscreen lookup.
- The starting save has known journal state.
If the entry changes state but displays blank text, the path resolved. Continue with the localization reference. If a new state change behaves like an older build, retest from a pre-install save before editing the path again.
Focused vanilla checks
Observed in vanilla: extract base\journal\cooked_journal.journal from
your own installation and inspect only the needed branch. Useful comparisons
are contacts/dex/q003_flathead_price for contact paths and the SQ021 file path
under onscreens/emails/quests/side_quest/sq021_sick_dreams/... for a
component-5 file entry. Pair the latter with
base\quest\side_quests\sq021\phases\sq021_randys_room.questphase.
Do not publish the extracted journal, its complete serialization, or the vanilla questphase. Record the depot path, focused entry types, component indices, and the exact question the comparison answered.
Localization reference
Choose the localization route from the content owner. Cyberpunk 2077 does not resolve journal text, spoken lines, and scene choices through one shared table, even when all three values are called a string or locstring in an editor.
| Record | Value |
|---|---|
| Practical baseline | Cyberpunk 2077 2.31a, WolvenKit 8.19.0, ArchiveXL 1.27.0, RED4ext 1.30.0, redscript 0.5.31 |
| Lab 1–5 localization resources | Structurally validated |
| In-game presentation | Verify with the relevant lab’s UI, subtitle, audio, and reload checks |
The complete model and property tables are in Localization paths. This page is the lookup and failure reference.
Pick the owning path
| Content on screen | Source identity | Runtime owner | ArchiveXL registration |
|---|---|---|---|
| Quest title, phase, objective, description, journal mappin, phone UI, file, shard, or onscreen text | Textual LocalizationString.value | localizationPersistenceOnScreenEntries | localization.onscreens.<locale> |
| Spoken scene subtitle and VO | Unsigned numeric scnlocLocstringId.ruid | Subtitle map -> subtitle entries, plus VO map -> WEM | localization.subtitles.<locale> and localization.vomaps.<locale> |
| Scene choice label | Unsigned numeric scnlocLocstringId.ruid reached through a screenplay option | Scene-embedded scnlocLocStoreEmbedded | None; it is inside the .scene |
ArchiveXL locale keys use a spelling such as en-us. Embedded scene locStore
descriptors use identifiers such as en_us, pl_pl, and db_db. Preserve
the schema’s spelling; do not normalize one into the other.
Journal and onscreen UI
The exact join is textual:
journal LocalizationString.value
-> onscreen entry secondaryKey
-> femaleVariant / maleVariant
| Onscreen entry property | Contract used by Labs 1–5 |
|---|---|
| Root data type | localizationPersistenceOnScreenEntries |
| Entry type | localizationPersistenceOnScreenEntry |
primaryKey | 0 in the mod-added textual-key shape |
secondaryKey | Globally unique text equal to LocalizationString.value |
femaleVariant | Authored text in the supplied labs |
maleVariant | Optional override; empty in the supplied labs |
Register the cooked JsonResource beneath source\archive, not its
.json.json conversion source beneath source\raw:
localization:
onscreens:
en-us:
- mod\cqa\cqa001\localization\en-us\onscreens\cqa001.json
LocalizationString.unk1, a scene line RUID, and a localization
primaryKey are not replacements for the textual secondaryKey join.
Spoken scene lines
One unsigned line RUID must survive four joins:
scene screenplayStore.lines[].locstringId.ruid
+-> subtitle map -> subtitle-entry resource -> entry.stringId
+-> VO map -> locVoLineEntry.stringId -> femaleResPath / maleResPath -> WEM
| Resource or item | Decisive field |
|---|---|
scnscreenplayDialogLine | locstringId.ruid |
localizationPersistenceSubtitleMapEntry | subtitleFile soft resource reference |
localizationPersistenceSubtitleEntry | Matching stringId, femaleVariant, maleVariant |
locVoLineEntry | Matching stringId, femaleResPath, maleResPath |
| WEM | Cooked depot resource reached by the VO-map path |
The screenplay line’s separate itemId.id is local to that screenplay store
and is not the localization ID. Keep the full unsigned 64-bit RUID in the scene, subtitle
entries, and VO map.
ArchiveXL registers the subtitle map, not the subtitle-entry resource it references. It registers the VO map, not each WEM:
localization:
subtitles:
en-us:
- mod\cqa\cqa005\localization\en-us\subtitles\cqa005_subtitles_map.json
vomaps:
en-us:
- mod\cqa\cqa005\localization\en-us\vo\cqa005_vo.json
First Contact uses this exact identity and path set:
| Item | Value |
|---|---|
| Authored line key | cqa005_contact_line_0001 |
| Unsigned line RUID | 9638591835734011695 |
| Subtitle map | mod\cqa\cqa005\localization\en-us\subtitles\cqa005_subtitles_map.json |
| Subtitle entries | mod\cqa\cqa005\localization\en-us\subtitles\cqa005_subtitles.json |
| VO map | mod\cqa\cqa005\localization\en-us\vo\cqa005_vo.json |
| WEM | mod\cqa\cqa005\localization\en-us\vo\contact_i_85c3283507e7ef2f.wem |
The supplied scene, maps, subtitle entry, and WEM contract are
Structurally validated. Audible playback and subtitle presentation on the
exact cqa005 route are covered by the Lab 5 test guide. Male-versus-female
path selection remains Experimental even if that campaign passes, because
both fields point to the same WEM and the matrix does not exercise distinct
gender-path cases.
Scene choice labels
A choice is an entirely scene-local lookup:
scnChoiceNodeOption.screenplayOptionId.id
-> screenplayStore.options[].itemId.id
-> screenplayStore.options[].locstringId.ruid
-> locStore.vdEntries[] descriptor
-> descriptor.vpeIndex
-> locStore.vpEntries[] payload.content
| Field | Rule |
|---|---|
Choice caption | Authoring/debug CName; not authoritative displayed text |
| Descriptor identity | Match locale, locstring RUID, signature, and variant |
vpeIndex | Zero-based index into vpEntries[] |
variantId.ruid | Preserve between descriptor and intended payload |
| Numeric ordering | Compare unsigned numeric RUID values, not decimal strings |
Observed in vanilla: the four scene resources listed in the vanilla depot-path index group descriptors into locale blocks and sort unsigned locstring IDs within each inspected block. This supports a known-good authoring shape, not a universal theorem about every scene or locale set.
There is no ArchiveXL registration for an embedded choice locStore. Adding a choice RUID to onscreens, subtitles, or vomaps cannot repair its descriptor or payload join.
Symptom routing
| Symptom | Inspect first |
|---|---|
| Journal entry exists but title/objective is blank | LocalizationString.value -> onscreen secondaryKey; primaryKey: 0; localization.onscreens.<locale> registration |
| Only one player variant is correct | Both femaleVariant and maleVariant; test both intended player configurations |
| Spoken subtitle is absent | Scene line RUID -> subtitle stringId; subtitle-map registration; map’s subtitleFile path |
| Subtitle appears but audio is silent | Same RUID in VO map; both WEM resource paths; packed WEM; audio and ArchiveXL logs |
| Choice caption is right but menu text is blank or stale | Option ID -> screenplay option -> locStore descriptor -> vpeIndex -> matching variant payload |
| Only one embedded locale is wrong | That locale’s descriptor block, numeric ordering, payload indices, and variant identities |
| A rebuilt string behaves like an older candidate | Installed archive/control-file hashes and a save made before the journal or scene became active |
Packing and a clean deserialize prove resource structure, not runtime lookup. Keep the archive and loose configuration together, inspect registration logs, and run journal tests from a pre-install save and scene tests from a save made before scene activation.
ArchiveXL registration reference
An archive makes a resource available at a depot path. ArchiveXL registration attaches selected mod-owned roots to game-owned systems. Most files in a quest are packed dependencies and must not receive their own top-level registration.
| Record | Value |
|---|---|
| ArchiveXL syntax baseline | 1.27.0 |
| Complete practical baseline | Cyberpunk 2077 2.31a, WolvenKit 8.19.0, ArchiveXL 1.27.0, RED4ext 1.30.0, redscript 0.5.31 |
| Lab 1–5 registration files | Structurally validated |
| In-game registration | Verify with the relevant lab’s clean-save test and ArchiveXL log checks |
This reference covers only the roots exercised by Labs 1–5. It is not a catalog of every ArchiveXL feature.
For custom effect spawners, use the separate
entity patching procedure.
Its resource.patch mapping adds the spawner to the named entity templates;
the .effect, particles, and textures remain referenced archive dependencies.
Registered roots versus packed dependencies
| Concern | Register this | Leave these as packed dependencies |
|---|---|---|
| Quest composition | One intended root questphase under quest.phases | External child questphases reached through phaseResource |
| Journal | Each contributed .journal under journal | Journal entries and handles inside that resource |
| Journal/UI text | Cooked onscreen localization resource under localization.onscreens.<locale> | Journal resources that carry its textual keys |
| Spoken subtitles | Subtitle map under localization.subtitles.<locale> | Subtitle-entry resource reached by the map |
| Spoken VO | VO map under localization.vomaps.<locale> | WEM files reached by map entries |
| World streaming | One .streamingblock under streaming.blocks | Sector files reached by block descriptors, plus resources inside those sectors |
| Scene | Nothing in the Lab 5 ArchiveXL file | .scene reached through the quest scene node’s resource path |
Registration and depot resolution are different edges. A root can register successfully while its child phase, sector, scene, subtitle entries, or WEM is missing from the archive.
Exact Lab 5 shape
Lab 5 exercises every registration family used in the first five labs:
quest:
phases:
- path: mod\cqa\cqa005\phases\cqa005.questphase
parent: base\quest\cyberpunk2077.quest
journal:
- mod\cqa\cqa005\journal\cqa005.journal
localization:
onscreens:
en-us:
- mod\cqa\cqa005\localization\en-us\onscreens\cqa005_onscreens.json
subtitles:
en-us:
- mod\cqa\cqa005\localization\en-us\subtitles\cqa005_subtitles_map.json
vomaps:
en-us:
- mod\cqa\cqa005\localization\en-us\vo\cqa005_vo.json
streaming:
blocks:
- mod\cqa\cqa005\world\cqa005_first_contact.streamingblock
The YAML values are REDengine depot paths relative to source\archive. They
are not Windows paths, project paths, or paths beneath source\raw.
Quest root registration
quest:
phases:
- path: mod\myquest\phases\myquest.questphase
parent: base\quest\cyberpunk2077.quest
| Key | Meaning |
|---|---|
path | Packed depot path of the mod-owned composition root |
parent | Game-owned quest resource below which ArchiveXL attaches it |
base\quest\cyberpunk2077.quest is a reference to a vanilla resource. Do not
extract it into the mod or include it in the archive.
For a parent and external child:
ArchiveXL quest.phases
-> myquest.questphase
questPhaseNodeDefinition.phaseResource (Soft)
-> myquest_activity.questphase
Register only myquest.questphase. Pack the child at the exact
phaseResource.DepotPath. Registering the child as another quest.phases
entry turns it into an additional root attachment; it does not merely satisfy
the parent’s resource reference.
Journal and onscreen registration
journal:
- mod\myquest\journal\myquest.journal
localization:
onscreens:
en-us:
- mod\myquest\localization\en-us\onscreens\myquest.json
The journal registration merges entry definitions. The onscreen registration
merges separately owned display text. A valid journal path does not imply that
its LocalizationString.value resolves, and a merged localization entry does
not create a journal entry.
Use Journal path reference and Localization reference to validate the two joins independently.
Subtitle and VO registration
localization:
subtitles:
en-us:
- mod\myquest\localization\en-us\subtitles\myquest_subtitles_map.json
vomaps:
en-us:
- mod\myquest\localization\en-us\vo\myquest_vo.json
The subtitle map owns a resource reference to the subtitle-entry CR2W. The VO map owns resource paths to WEMs. Therefore all of these must be packed, but only the two maps appear in the ArchiveXL YAML:
registered subtitle map
-> packed subtitle entries
registered VO map
-> packed WEM
Do not register the subtitle-entry file in place of the map. Do not add a WEM path as though it were another VO map.
Streaming registration
streaming:
blocks:
- mod\myquest\world\myquest.streamingblock
The streaming block’s descriptors reach the packed sectors:
registered streaming block
+-> Quest streaming sector
+-> AlwaysLoaded streaming sector
Register the block once. Do not add its .streamingsector files to
streaming.blocks. A clean block registration still cannot repair a wrong
sector depot path, descriptor category, bounds, quest-prefab root, NodeRef, or
world identity.
Filesystem and deployment
The WolvenKit project separates the two delivery channels:
source\archive\... cooked resources packed into ModName.archive
source\resources\... loose ModName.archive.xl configuration
After a normal install, both top-level payloads are expected beneath the game directory:
<Cyberpunk 2077>\archive\pc\mod\ModName.archive
<Cyberpunk 2077>\archive\pc\mod\ModName.archive.xl
Do not author in WolvenKit’s generated packed directory. Repacking may
replace it.
Verification order
- Close the game and framework processes before replacing either payload.
- Inventory installed candidates; do not leave a start checkpoint, completed checkpoint, or older archive with the same depot paths enabled.
- Confirm every registered value is an exact cooked depot path and every referenced dependency is present in the archive.
- Confirm
quest.phasescontains only the intended composition root or roots. - Confirm subtitle registration names the map, streaming registration names the block, and neither indirect leaf is added as a root.
- Pack and list the archive’s depot paths.
- Install the
.archiveand matching.archive.xl; keep the pair together. - Start the game and inspect RED4ext and ArchiveXL logs for load, registration, merge, and missing-resource errors.
- Run from a save created before the relevant quest, journal, scene, community, and device state existed.
- Remove the exact pair with the game closed and repeat the documented isolation case where the lab requires one.
A file appearing under archive\pc\mod proves deployment only. A clean
ArchiveXL log proves the configuration was processed, not that graph execution
or an indirect resource works.
Failure routing
| Symptom | Inspect first |
|---|---|
| Root quest never starts | YAML nesting, root depot path, parent, installed .archive.xl, ArchiveXL log, and starting save |
| Parent starts but child never resolves | Packed child path and parent phaseResource; do not add a second root registration as a shortcut |
| Journal transition node runs but no entry appears | Journal registration, typed journal path, and save-backed state |
| Journal entry exists but text is blank | Onscreen registration and localization key join |
| Subtitle is absent | Registered subtitle map, its subtitleFile reference, and line RUID |
| Subtitle works but audio is silent | Registered VO map, WEM path and payload, line RUID, and audio logs |
| World content never appears | Registered block, descriptor-to-sector paths, bounds/category, and quest-prefab scope |
| Scene path cannot load | Packed .scene and quest node soft resource path; no separate Lab 5 scene registration exists |
| Old behavior survives a rebuild | Duplicate installed candidate, mismatched archive/YAML hashes, or save-backed graph/journal/scene/world state |
The supplied Lab 1–5 YAML and dependency sets are Structurally validated with the pinned tooling. Their exact in-game merges remain Experimental until the corresponding canonical clean-save records pass.
Lab status
The supplied lab resources are Structurally validated with WolvenKit
8.19.0. Their in-game test campaigns are pending.
Lab 1 runtime evidence: Experimental — pending.
Lab 2 runtime evidence: Experimental — pending.
Lab 3 runtime evidence: Experimental — pending.
Lab 4 runtime evidence: Experimental — pending.
Lab 5 runtime evidence: Experimental — pending.
| Lab | Test guide |
|---|---|
| First Signal | Install, test, and reset |
| Signal Race | Test both routes |
| Boundary Check | Test Boundary Check |
| Handoff Point | Test Handoff Point |
| First Contact | Test First Contact |
Use the tested versions and the starting saves specified by each guide.
Vanilla depot-path index
These are focused Observed in vanilla research anchors used by the book. Extract them from your own Cyberpunk 2077 installation with WolvenKit, answer one narrow question, and keep the extracted files out of distributable projects.
The workflow is in Inspect a vanilla questphase. A path in this index is a citation, not a template and not a file supplied by this repository.
| Record | Value |
|---|---|
| Practical comparison target | Cyberpunk 2077 2.31a, WolvenKit 8.19.0 |
| Evidence class | Observed in vanilla, limited to the question named for each path |
Core roots, journal, and localization
| Depot path | Inspect it for |
|---|---|
base\quest\cyberpunk2077.quest | Game-owned parent used by ArchiveXL root-questphase registration; cite it, do not add an extracted copy to the mod |
base\journal\descriptor.journaldesc | Standard soft descriptor reference used by contributed journal roots |
base\journal\cooked_journal.journal | Focused journal entry families, typed paths, file-entry indices, mappins, contacts, messages, files, and saved presentation targets |
base\localization\en-us\onscreens\onscreens.json | Focused English onscreen key lookup, including the Allen Street fast-travel label comparison |
The cooked journal is large. Search for one path and record only its entry types, IDs, and relevant properties. Do not publish its complete serialization.
Questphases and control patterns
| Depot path | Focused question |
|---|---|
base\quest\side_quests\sq021\phases\sq021_randys_room.questphase | How a computer-page output sets a fact and how a phase waits for that authored document signal |
base\quest\main_quests\part1\q108\phases\q108_06b_tower_mainframe.questphase | Prefab dependencies, trigger/device/scene-marker/AI-spot refs, Boolean gates, and a plant-item comparison |
base\open_world\minor_activities\watson\northside\ma_wat_nid_15\ma_wat_nid_15_phase.questphase | Facts, logical trees, trigger/time/journal/content conditions, and investigation progression |
base\open_world\minor_activities\watson\little_china\ma_wat_lch_03\ma_wat_lch_03_phase.questphase | A focused read_shard objective whose progression does not use questJournalEntryVisited_ConditionType |
base\open_world\minor_activities\watson\little_china\ma_wat_lch_05\ma_wat_lch_05_phase.questphase | A second focused read_shard comparison without a journal-visited condition |
base\open_world\minor_activities\watson\little_china\ma_wat_lch_15\phases\ma_wat_lch_15.questphase | A third focused read_shard comparison using inventory, loot, or interaction state rather than a universal visited recipe |
base\quest\side_quests\sq011\phases\sq011_concert.questphase | Game-time and real-time payloads plus delayed contact orchestration |
base\quest\side_quests\sq011\phases\sq011_follow_up.questphase | Multi-day game-time delay before later contact logic |
base\open_world\street_stories\westbrook\japantown\sts_wbr_jpn_03\phase\sts_wbr_jpn_03_streetstory.questphase | Parallel stealth-failure monitoring, stop/converge behavior, and optional outcomes |
base\open_world\street_stories\westbrook\japantown\sts_wbr_jpn_03\phase\sts_wbr_jpn_03_combat.questphase | Combat-side stealth outcome handling and cleanup context |
base\open_world\street_stories\watson\little_china\sts_wat_lch_01\phase\sts_wat_lch_01_phase.questphase | Parent prefab scope and external-child composition |
base\open_world\street_stories\watson\little_china\sts_wat_lch_01\phase\sts_wat_lch_01_openworld.questphase | Device interaction, upload, and scan condition shapes |
base\open_world\street_stories\watson\little_china\sts_wat_lch_01\phase\sts_wat_lch_01_combat.questphase | Device, killed, spawned, workspot, quickhack, and combat lifecycle shapes |
base\open_world\street_stories\watson\northside_industrial_district\sts_wat_nid_03\phases\sts_wat_nid_03.questphase | A parent that owns one prefab while external children use nested world refs |
base\open_world\street_stories\watson\northside_industrial_district\sts_wat_nid_03\phases\sts_wat_nid_03_openworld.questphase | Empty child phasePrefabs, destruction/device/combat/vehicle conditions, and nested refs |
base\open_world\street_stories\watson\northside_industrial_district\sts_wat_nid_03\phases\sts_wat_nid_03_gameplay.questphase | Escort movement and ordered destination-gate research |
base\open_world\street_stories\heywood\vista_del_rey\sts_hey_rey_09\phases\sts_hey_rey_09_openworld.questphase | Inventory, scan, scene, character-mount/carry, and vehicle-trunk condition shapes |
base\open_world\street_stories\westbrook\japantown\sts_wbr_jpn_09\phases\sts_wbr_jpn_09_gameplay.questphase | Spawner readiness, distance, mount, and defend-target comparison |
base\open_world\street_stories\watson\kabuki\sts_wat_kab_05\phases\sts_wat_kab_05_openworld.questphase | Journal completion/reward context and native drop-point reservation comparison |
base\quest\side_quests\sq004\phases\sq004_02_drive.questphase | Player/contact mount conditions, vehicle assignment, seat roles, and destination-driving comparison |
base\quest\side_quests\sq031\phases\sq031_rogue.questphase | Spawned readiness, puppet-AI tier work, and vehicle-forbidden-trigger context |
base\quest\side_quests\sq031\phases\sq031_porsche.questphase | Player-vehicle record enable/despawn payload context; not a generic world-vehicle cleanup operation |
base\open_world\street_stories\watson\northside_industrial_district\sts_wat_nid_02\phases\sts_wat_nid_02_gameplay.questphase | Player-trigger contrasts and a van CT_NOT_EQUAL/0 moving-state condition, not an exact stopped-inside delivery chain |
base\open_world\street_stories\badlands\inland_avenue\sts_bls_ina_07\phases\sts_bls_ina_07_gpl.questphase | Composite chase staging: vehicle-seat assignment, AI roles, and route/lost-state facts |
base\quest\side_quests\sq024\phases\sq024_05_the_big_race.questphase | Race start/stop/recovery, course splines, competitor parameters, checkpoints, and restriction lifecycle |
base\quest\side_quests\sq004\phases\sq004_03_raffen_shiv_camp.questphase | Release/escort comparison, target readiness, follower AI, and later handoff context |
base\open_world\street_stories\watson\kabuki\sts_wat_kab_02\phases\sts_wat_kab_02_openworld.questphase | Device release plus named-target readiness comparison |
base\open_world\street_stories\santo_domingo\arroyo\sts_std_arr_05\phases\sts_std_arr_05_openworld.questphase | Plant-item device action/condition and inventory-consumption comparison |
base\open_world\phases\cyberpsychos\open_world_cyberpsychos.questphase | Shared cyberpsycho orchestration, reveal/resolution families, and metaquest ownership; not a complete custom encounter recipe |
These phases depend on quest-local facts, journal entries, NodeRefs, communities, devices, vehicles, and other child resources. A copied node is not a self-contained recipe.
Scenes, communities, and presentation
| Depot path | Focused question |
|---|---|
base\quest\minor_quests\mq003\mq003_orbitals.questphase | Whole-community activation, spawned readiness, and activity-phase invocation |
base\quest\minor_quests\mq003\phases\mq003_homeless.questphase | Named community phase changes and later deactivation |
base\quest\minor_quests\mq003\scenes\mq003_01_homeless.scene | Community actor acquisition, entry/exit shape, performers, screenplay, events, and embedded localization ordering |
base\quest\minor_quests\mq003\scenes\mq003_03_orbital_pod.scene | Comparable scene resource, actor, screenplay, event, and locStore structures |
base\quest\minor_quests\mq007\scenes\mq007_01_gun_found.scene | Comparable scene graph and embedded locStore ordering |
base\quest\minor_quests\mq010\scenes\mq010_02_barry_talk.scene | Comparable dialogue scene and embedded locStore ordering |
base\animations\quest\minor_quests\mq007\anim\body\mq007__talking_gun__male_fpp.anims | Direct scene-animation resource ownership and event-reference comparison; not a reusable custom clip |
base\localization\en-us\lipsync\base\quest\minor_quests\mq007\scenes\mq007_01_gun_found\skippy.anims | English Skippy lipsync-set resource selected by the scene’s lipsync reference row |
base\localization\en-us\lipsync\base\quest\minor_quests\mq007\scenes\mq007_01_gun_found\v.anims | Separate English V lipsync-set resource and performer-slot/cardinality comparison |
base\quest\side_quests\sq011\scenes\sq011_09_nancy_call.scene | Holocall scene invocation, entry/exit, and quest handoff comparison |
base\open_world\minor_activities\westbrook\japantown\ma_wbr_jpn_13\community\ma_wbr_jpn_013_claws_com.community | Generic Tyger Claw entries, phases, appearances, time periods, quantities, and AI-spot references |
ep1\characters\entities\gang\gang__ep1_tyger_wa.ent | Entity-template appearance mappings exposed by the focused Character.* record inheritance chain |
base\characters\appearances\gang\gang__tyger_wa.app | Internal appearance definitions selected by the focused entity’s exposed mapping; not a portable appearance template |
base\workspots\common\ground\generic__stand_ground_cigarette__smoke__01.workspot | The stationary cigarette workspot used by the Lab 5 contact |
base\workspots\common\ground\generic__stand_ground__guard__02.workspot | A different standing-guard lineage; do not substitute it for the cigarette mapping without new evidence |
base\workspots\common\wall\generic__stand_wall_lean_left__stand_around__01.workspot | A scene-referenced wall-lean workspot used to distinguish workspot acquisition from direct animation events |
base\workspots\patrolling\guard_stand.workspot | Finite-patrol research only; not a universal guard workspot |
base\animations\facial\generic\interactive_scene\generic_facial_lipsync_gestures.anims | Generic scene lipsync resource row and slot-cardinality comparison, not proof of facial quality |
Keep actor ID, performer ID, screenplay item ID, localization RUID, event ID, community identity, AI-spot identity, NodeRef, and CR2W handle separate while recording a comparison.
Braindance resources
| Depot path | Focused question |
|---|---|
base\quest\side_quests\sq012\scenes\sq012_02a_braindance.scene | Rewindable section, RID handlers/references, support props, layered clue events, entry/exit, and interruption ownership |
base\animations\quest\side_quests\sq012\sq012_braindance\rid\sq012_braindance__part_a.scenerid | Version-5 RID actor/camera tags, body/facial/cyberware channels, serials, cardinalities, and compressed buffers |
base\quest\side_quests\sq012\entities\sq012_02a_braindance_bdview.ent | BD-view entity template selected through a scene prop record and global spawn marker |
base\quest\side_quests\sq012\entities\sq012_02a_braindance_bdfog.ent | BD-fog entity template selected through a separate scene prop record |
base\quest\side_quests\sq012\entities\sq012_02a_braindance_bdsetup.ent | Render-to-texture BD-setup entity template selected through a separate scene prop record |
base\quest\main_quests\prologue\q004\scenes\q004_05_bd_yorinobu.scene | Larger main-quest rewindable scene with visual/audio/thermal conditions, clue discovery, RID playback, visibility, and BD action management |
base\quest\main_quests\prologue\q004\phases\q004_braindance.questphase | Quest-side setup/teardown decomposition around the Q004 braindance; not a self-contained custom-scene template |
engine\scenesystem\camera.ent | Scene-camera entity shape used for a placed RID-camera comparison; it does not supply a candidate’s world placement |
World, placement, and devices
| Depot path | Focused question |
|---|---|
base\worlds\03_night_city\_compiled\default\03_night_city.streamingworld | Global Night City resource roots and compiled block ownership |
base\worlds\03_night_city\_compiled\default\blocks\all.streamingblock | Sector descriptors, categories, bounds, levels, and quest-prefab roots |
base\worlds\03_night_city\_compiled\default\quest_606b61008df2ba6f.streamingsector | Focused Quest-sector nodes, placements, NodeRefs, trigger data, and prefab namespace |
base\worlds\03_night_city\_compiled\default\always_loaded_0.streamingsector | AlwaysLoaded references and marker-related content without assuming compact array pairing |
base\worlds\03_night_city\_compiled\default\exterior_-18_28_0_0.streamingsector | Ambient terminal placement comparison |
base\worlds\03_night_city\_compiled\default\bd21168eed6c6d62.streamingsector_inplace | Separate inplace payload associated with that focused terminal placement |
base\worlds\03_night_city\_compiled\default\exterior_-17_22_0_0.streamingsector | Allen Street fast-travel terminal placement, transform, entity template, and NodeRef chain |
base\worlds\03_night_city\_compiled\default\always_loaded_1.streamingsector | Allen Street destination-marker registration and placement |
base\gameplay\devices\fast_travel\data_term_1.ent | Entity template used by the focused fast-travel placement |
base\worlds\03_night_city\_compiled\default\exterior_19_-8_0_0.streamingsector | Placed laptop node data, entity template, instance data, and cooked prefab link |
base\worlds\03_night_city\_compiled\default\4fd0915183681e53.streamingsector_inplace | What the laptop placement’s separate inplace resource embeds and omits |
base\gameplay\devices\drop_points\drop_point.ent | Drop-point components and local UI/navigation slots |
base\gameplay\devices\masters\computers\laptop_1.ent | Base computer components, controller package, and interaction/workspot ownership used by focused laptop comparisons |
base\worlds\03_night_city\_compiled\default\03_night_city.devices | Focused lookup of a device identity in the global device registry |
World identities and transforms are not portable guarantees. Reusing a vanilla location still requires measured geometry, streaming tests, collision and navmesh checks, and save-aware device/community acceptance.
Custom effects
| Depot path | Inspect it for |
|---|---|
base\fx\player\status_effects\blindness\p_blindness.particle | Screen particle drawer, emitter modules, local material, and compiled render data |
base\fx\player\p_hacking_glitch\p_hacking_glitch_low.effect | worldEffect timeline, tracks, event objects, and loop data |
engine\materials\particles_generic.remt | Textured-particle material parameters |
base\characters\entities\player\player_ma_fpp.ent | Male-average player first-person components and slots |
base\characters\entities\player\player_wa_fpp.ent | Female-average player first-person components and slots |
base\characters\entities\player\player_ma_tpp.ent | Male-average player third-person components and slots |
base\characters\entities\player\player_wa_tpp.ent | Female-average player third-person components and slots |
See Custom effects for the resource chain and authoring procedure.
Inspect a reference
Extract the named resource from your own installation before comparing fields.
For every focused inspection, record:
- the exact depot path and installed game build;
- the root type and only the fields or graph edges needed to answer the question;
- the identifier domains and external dependencies involved;
- whether the shape was observed once or across multiple resources;
- what the observation does not prove.
Do not commit extracted vanilla CR2Ws, complete serialized resources, or assets derived by copying their payloads. Cite the depot path and teach the reader how to reproduce the inspection.
Tested versions
Use these versions for the lab procedures.
| Component | Pinned version | Role |
|---|---|---|
| Cyberpunk 2077 for Windows | 2.31a | Exact GOG Windows build; CD Projekt RED calls the underlying public patch 2.31 |
| WolvenKit | 8.19.0 | Project editing, CR2W inspection, conversion, packing, and deployment |
| ArchiveXL | 1.27.0 | Root questphase, journal, localization, and streaming-block registration |
| RED4ext | 1.30.0 | Runtime required by ArchiveXL |
| redscript | 0.5.31 | Runtime dependency declared by ArchiveXL 1.27.0, even though Labs 1–5 contain no .reds source |
| TweakXL | 1.11.3 | Scope-specific pin for registering mod-owned Character.* and other TweakDB records in advanced guides; not required by Labs 1–5 |
Version check date: 2026-08-09. The ArchiveXL 1.27.0 compatibility contract
requires Cyberpunk 2077 2.31, redscript 0.5.31 or later, and RED4ext 1.29.0 or
later. This book pins the exact versions above instead of treating those
minimums as a tested range. Cyberpunk 2077 2.31a was the Windows GOG build
supplied for testing.
TweakXL 1.11.3 belongs only to procedures that add mod-owned TweakDB
records. Inspecting vanilla records and completing Labs 1–5 does not require
it. A page that uses TweakXL states that dependency locally instead of
silently expanding every reader workflow.
These are the initial support baseline, not universal minimums. A practical chapter may name a newer baseline after its example has been repeated against that combination. Older versions are unsupported unless the chapter says otherwise.
The 2.31a suffix is visible in the Windows GOG distribution. CD Projekt RED’s
public notes are titled Patch
2.31; this book records the
exact installed build so results are reproducible.
Release sources:
Tutorial namespace
The tutorials use the neutral prefix cqa, short for Cyberpunk Quest
Authoring.
| Domain | Lab 1 value | Rule |
|---|---|---|
| Mod namespace | cqa | Shared namespace for tutorial-owned resources |
| Quest ID | cqa001 | Three-letter prefix plus a three-digit lab number |
| Depot root | mod\cqa\cqa001\ | Keeps every Lab 1 resource under one mod-owned root |
| Completion fact | cqa001_completed | Quest ID first; descriptive suffix second |
| Localization keys | cqa_cqa001_* | Namespace and quest remain visible in global lookup tables |
| WolvenKit project | CQA_Lab01_OneShot | Human-readable checkpoint name |
Each numbered lab is a separate installed quest and uses its own ID. Lab 2 is
cqa002 / CQA_Lab02_SignalRace; Lab 3 is cqa003 /
CQA_Lab03_BoundaryCheck; Lab 4 is cqa004 /
CQA_Lab04_HandoffPoint; Lab 5 is cqa005 /
CQA_Lab05_FirstContact. Start and completed checkpoints within one lab keep
the same depot root, so they are alternatives and must not be installed
together.
Lab 4 keeps both questphases and every supporting resource under
mod\cqa\cqa004\. Its sole persistent fact is cqa004_completed. The root
prefab NodeRef is #cqa004_pr_handoff; the marker and trigger child refs are
#cqa004_mp_handoff, #cqa004_tr_reach, and #cqa004_tr_leave. These names
belong to the tutorial resource set; they are not vanilla depot paths.
Lab 5 keeps its root, external child, scene, journal, three external spoken-line
localization resources, streaming block, two sectors, and WEM beneath
mod\cqa\cqa005\. Its completion fact is cqa005_completed, and its root
prefab is #cqa005_pr_first_contact. The local child names are:
#cqa005_tr_setup
#cqa005_tr_cleanup
#cqa005_spot_contact
#cqa005_com_contact
#cqa005_sm_contact
#cqa005_mp_contact
The spoken-line key cqa005_contact_line_0001 deterministically identifies
unsigned locstring 9638591835734011695. That locstring domain is separate
from quest facts, scene nodes, world identities, and localization secondary
keys.
The prefix is intentionally not q, mq, sq, sts, or another
vanilla-looking family. Tutorial resources should be recognizable as mod-owned
when they appear in logs or depot browsers.
These names are stable book conventions, not game requirements. Authors should choose a distinct prefix for their own released mods to avoid collisions with the downloadable tutorials.
Reading graph diagrams
Graph diagrams show node IDs, concrete RED types, socket names, and connections. Match these to your WolvenKit resource; editor positions and CR2W handle numbers can differ.
Follow execution
- Follow solid arrows for normal execution.
- Read the source and destination socket names beside each connection.
- Follow dashed red arrows for cut or interruption routes.
- Treat dotted links as resource lookups rather than execution edges.
- Check node-detail tables for conditions and properties too large to fit in the figure.
Distinguish the views
| View | Use it to understand |
|---|---|
| Story flow | What the player does and sees |
| Exact node graph | Which node types and sockets implement the route |
| Resource map | Which files own the graph, world objects, text, or actors |
A graph node ID identifies the node. A CR2W handle connects objects inside one resource, while a NodeRef locates a world object. See Identifier domains before comparing numbers across these views.