Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

  1. First Signal — questphase, journal, localization, and a persistent fact.
  2. Signal Race — conditions, waits, Boolean trees, and parallel signal flow.
  3. Boundary Check — streaming sectors, NodeRefs, triggers, and a map pin.
  4. Handoff Point — root and child questphases with explicit input/output contracts.
  5. 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:

  • .questphase graphs coordinate progression and persistent state;
  • .journal resources 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;
  • .scene resources 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:

ResponsibilityOwner in Lab 1
Execution, wait, fact guard, and terminationcqa001.questphase
Quest and objective definitionscqa001.journal
English player-facing textcqa001 onscreen localization resource
Attaching those mod-owned resources to game rootsCQA_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

  1. Install the pinned toolchain from a known starting state.
  2. Create a project and understand its layers.
  3. Extract and inspect one named vanilla questphase in a separate research project.
  4. Read Foundations for ownership, graph execution, identifiers, save-backed state, composition, and lifecycle.
  5. Build Lab 1: First Signal.
  6. 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.

RecordValue
Game baselineCyberpunk 2077 2.31a, Windows GOG build
Authoring toolWolvenKit 8.19.0
Resource frameworkArchiveXL 1.27.0
Runtime loaderRED4ext 1.30.0
ArchiveXL script requirementredscript 0.5.31
Runtime test dateNot 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

  1. Install Microsoft’s .NET 8 runtime. The WolvenKit 8.19.0 release names .NET 8 as its prerequisite.

  2. From that release, download WolvenKit-8.19.0.zip or WolvenKitSetup-8.19.0.exe. Do not download GitHub’s automatically generated “Source code” archives.

  3. 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.

  4. Start WolvenKit.exe. On first launch, set Game Executable Path to the actual Cyberpunk2077.exe:

    <Cyberpunk 2077>\bin\x64\Cyberpunk2077.exe
    
  5. Set Depot Path to a writable cache directory outside both the game and your projects, for example C:\CyberpunkModding\WolvenKitDepot.

  6. 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.

  1. Install the Microsoft Visual C++ 2015–2022 x64 Redistributable required by RED4ext.
  2. Download red4ext-1.30.0.zip from the RED4ext 1.30.0 release and extract it into the Cyberpunk 2077 directory. Do not use the separate symbols archive.
  3. Download redscript-v0.5.31-windows.zip from the redscript 0.5.31 release and extract it into the same directory.
  4. Download ArchiveXL-1.27.0.zip from 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.

RecordValue
WolvenKit8.19.0
Runtime baselineCyberpunk 2077 2.31a, ArchiveXL 1.27.0, RED4ext 1.30.0, redscript 0.5.31
Runtime test dateNot 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:

FieldLab 1 value
Project nameCQA Lab 01 One Shot
Creation locationA writable projects directory outside Cyberpunk 2077
Mod nameCQA_Lab01_OneShot
AuthorYour name
Version0.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:

DomainExampleMeaning
Game filesystem pathC:\Games\Cyberpunk 2077\archive\pc\modA location Windows can open
Project filesystem pathC:\CyberpunkModding\Projects\CQA_Lab01_OneShot\source\archiveAuthoring files owned by this project
WolvenKit cache/depot directoryC:\CyberpunkModding\WolvenKitDepotExtracted/cache data used by WolvenKit
REDengine depot pathmod\cqa\cqa001\phases\cqa001.questphaseA 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:

FileRoot responsibility
.questphaseQuest graph, nodes, sockets, handles, phase interfaces
.journalJournal entry hierarchy and entry state targets
localization .json CR2WCooked 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:

  1. the .cpmodproj is outside the game directory;
  2. the mod name is unique and stable;
  3. every cooked resource will live below source\archive at its intended depot path;
  4. loose .archive.xl configuration will live below source\resources;
  5. nothing below source\raw is 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.

RecordValue
Extraction targetCyberpunk 2077 2.31a Windows archives
Inspection toolWolvenKit 8.19.0
Runtime testNot 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 pinned 2.31a installation 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:

QuestionWhat 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:

NodeFocused observation
175Computer manager enables UI interactivity for #sq021_randy_pc
176Waits for sq021_randy_pc_webfile_found > 0
177Disables computer UI interactivity
182Activates journal page internet_sites/drugs_are_bad/05_secret_page
191Sets sq021_thisisfucked_read = 1 from the computer-page output
192Waits until sq021_thisisfucked_read > 0
193Sets 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:

  1. the Windows path and depot path are different;
  2. adding the file to a project extracts a distributable cooked resource;
  3. a vanilla phase’s external references do not become mod-owned dependencies;
  4. node sockets, not visual placement, establish execution;
  5. 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:

  1. Resources and ownership
  2. Graph execution
  3. Identifier domains
  4. Facts, journals, and saves
  5. Root and child questphases
  6. 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

LayerTypical resourcesOwns
Quest control.questphaseExecution flow, conditions, journal operations, facts, child-phase and scene handoff
Journal and UI.journalQuest, phase, objective, description, message, file, and map-pin entries
Localizationonscreen JSON resources, subtitle maps, VO maps, embedded scene locStorePlayer-facing text through separate lookup systems
Scenes.scene, lipsync and animation referencesDialogue, choices, performers, timed events, entry points, and named exits
Worldstreaming blocks, streaming sectors, inplace resources, .devicesTriggers, markers, communities, AI spots, devices, and NodeRef registration
Characters.ent, .app, TweakDB records, community entriesEntity composition, appearances, gameplay records, and spawned identities
Framework registrationArchiveXL and related loose configurationMerging 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 ResourcePath value 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:

OutcomeOwner
Run once, wait, and finishcqa001.questphase
Define the quest and objectivecqa001.journal
Display “First Signal” and “Wait for the signal.”cqa001 onscreen localization
Attach all three to game rootsCQA_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:

  1. Does the owning resource exist at the exact depot path?
  2. Was that resource packed or staged in the correct installation layer?
  3. Was it registered or merged where required?
  4. Does the referring resource use the correct identifier domain?
  5. 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, or False;
  • a direction or role such as Input, Output, or CutDestination;
  • 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:

  1. In1 emits Out into the one-shot guard’s In.
  2. True enters the quest journal node through Active.
  3. Each ordinary Out advances the first-run sequence.
  4. The delay’s Out enters the objective through Succeeded.
  5. The final quest journal operation enters through Succeeded.
  6. Both the guard’s False route 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

DomainTypical serialized shapeIdentifies
Depot pathResourcePathA resource in the virtual depot
CR2W handleHandleId / HandleRefIdAn object inside one serialized CR2W object graph
Quest graph nodenumeric node idA node inside one questphase graph
World referenceNodeRefA registered world or quest-prefab object
Name tokenCNameA REDengine name value such as a socket or entry-point name
Gameplay recordTweakDBIDA record in TweakDB
Factstring factNameA signed integer state slot in the facts database
Journal pathgameJournalPath.realPathAn entry in the merged journal tree
Localization keysecondary key or numeric locstring IDText in one of several localization lookup systems
Scene-local IDsactor, performer, node, event, screenplay, locstring, and variant IDsDistinct 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 HandleRefId must resolve to one compatible HandleId;
  • 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 shapeMeaning chosen by the author
0 / 1false/true, not started/completed, inactive/active
increasing integerstage number, counter, or threshold
distinct named factsindependent 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:

  1. First load: start from the known pre-mod save and observe activation.
  2. Mid-flow reload: save while the objective is active, reload, and record whether the intended node resumes.
  3. Completed reload: save after completion, reload, and verify no reactivation.
  4. Reinstall same build: prove that reinstalling does not change the result already stored in the save.
  5. 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 phasePrefabs declares 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 phaseInstancePrefabs array. 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:

  1. Does every intended child route reach an output?
  2. Do the output names match the parent phase node?
  3. Does every parent output lead somewhere intentional?
  4. 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:

PolicyRequired control
One-shotPersistent completion guard and a terminating already-done route
ResumeSave-safe stage state and a route back into the correct activity
Retry after failureFailure cleanup plus a deliberate reset boundary
RepeatableState 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:

CheckWhat it provesWhat it does not prove
WolvenKit save or CR2W deserializationThe editor/converter accepted the serialized structureRuntime references and behavior
CR2W round tripImportant structure survives binary serializationThat the game can execute it
Handle and graph validationReferences, nodes, sockets, and edges are internally consistentExternal resource existence
Archive packing and path listingIntended CR2W payloads entered the archive at known depot pathsLoose framework files were installed
ArchiveXL log registrationThe framework saw and registered configured resourcesPlayer-facing behavior
Focused runtime routeThe tested behavior occurred in one environment and save stateReload, interruption, or replay safety
Clean-save acceptance matrixThe documented lifecycle cases pass from controlled stateCompatibility 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:

  1. which resource owns every referenced object or string;
  2. how execution enters and leaves every node;
  3. the type and domain of every identifier;
  4. which state can already exist in the save;
  5. who owns cleanup and termination;
  6. 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.

RecordValue
Structural validation date2026-07-27
Runtime test dateNot 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:

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:

ResourceDepot pathOwns
Root questphasemod\cqa\cqa001\phases\cqa001.questphaseExecution order, one-shot guard, delay, journal state changes, completion fact, and termination
Journalmod\cqa\cqa001\journal\cqa001.journalQuest, phase, and objective entries
Onscreen localizationmod\cqa\cqa001\localization\en-us\onscreens\cqa001.jsonPlayer-facing English strings referenced by journal entries
ArchiveXL registrationCQA_Lab01_OneShot.archive.xlAttaches 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:

KeyEnglish text
cqa_cqa001_titleFirst Signal
cqa_cqa001_objective_waitWait 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.

IDRED node typeIncoming socketDecisive propertyPurpose
0questInputNodeDefinitionroot In1socket name In1Entry from the registered root
10questConditionNodeDefinitionIncqa001_completed Equal 0Evaluate once and choose True or False immediately
11questJournalNodeDefinitionActivepath quests/minor_quest/cqa001Activate and track the quest
12questJournalNodeDefinitionActiveobjective pathActivate and track the objective
13questPauseConditionNodeDefinitionInquestRealtimeDelay_ConditionType, 10 secondsHold this execution path until elapsed real time reaches ten seconds
14questJournalNodeDefinitionSucceededobjective pathMark the objective succeeded
15questFactsDBManagerNodeDefinitionInset cqa001_completed exactly to 1Persist one-shot completion state
16questJournalNodeDefinitionSucceededpath quests/minor_quest/cqa001Mark the quest succeeded
1questOutputNodeDefinitionInTerminatingEnd 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:

  1. start from a save that has never loaded cqa001;
  2. confirm the quest and objective activate once;
  3. confirm completion occurs after ten seconds of real time;
  4. confirm the objective and quest both show succeeded presentation;
  5. save, reload, and confirm the quest does not reactivate;
  6. 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 14 through Succeeded, not Active.
  • If the quest repeats, check cqa001_completed on 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.

RecordValue
Runtime test dateNot 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 requires 0.5.31 or 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

  1. Extract the start checkpoint to a writable working directory outside the game directory.

  2. In WolvenKit, choose File > Open Project and open CQA_Lab01_OneShot_Start.cpmodproj.

  3. Save a separate backup of that freshly extracted directory. WolvenKit’s quest editor has no general undo/redo safety net for this graph workflow.

  4. In the Project Explorer, open the source\archive directory 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 FileNameResult beneath source\archive
questQuestPhaseResourcemod\cqa\cqa001\phases\cqa001.questphaseRoot questphase
gameJournalResourcemod\cqa\cqa001\journal\cqa001.journalJournal tree
JsonResource with the .json descriptionmod\cqa\cqa001\localization\en-us\onscreens\cqa001.jsonOnscreen 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 RootChunkValue
cookingPlatformPLATFORM_PC
entry.descriptor.DepotPathbase\journal\descriptor.journaldesc
entry.descriptor.FlagsSoft
entry.entries[0].idquests
entry.entries[0].entries[0].idminor_quest
entry.entries[0].entries[0].entries[0].idcqa001
...cqa001.title.unk10
...cqa001.title.valuecqa_cqa001_title
...cqa001.typeMinorQuest
...cqa001.entries[0].idcqa001_01
...cqa001_01.entries[0].idcqa001_01_obj_wait
...cqa001_01_obj_wait.description.unk10
...cqa001_01_obj_wait.description.valuecqa_cqa001_objective_wait
...cqa001_01_obj_wait.counter0
...cqa001_01_obj_wait.optional0

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 RootChunkValue
cookingPlatformPLATFORM_PC
root.entries[0].primaryKey0
root.entries[0].secondaryKeycqa_cqa001_title
root.entries[0].femaleVariantFirst Signal
root.entries[0].maleVariantempty string
root.entries[1].primaryKey0
root.entries[1].secondaryKeycqa_cqa001_objective_wait
root.entries[1].femaleVariantWait for the signal.
root.entries[1].maleVariantempty 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 labelRED typeTarget graph ID
InputquestInputNodeDefinition0
OutputquestOutputNodeDefinition1
ConditionquestConditionNodeDefinition10
JournalquestJournalNodeDefinition11
JournalquestJournalNodeDefinition12
PauseConditionquestPauseConditionNodeDefinition13
JournalquestJournalNodeDefinition14
FactsDBManagerquestFactsDBManagerNodeDefinition15
JournalquestJournalNodeDefinition16

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:

NodePropertyValue
ID 0socketNameIn1
ID 1socketNameOut1
ID 1typeTerminating

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 pathValue
condition.type.factNamecqa001_completed
condition.type.comparisonTypeEqual
condition.type.value0

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 pathValue
type.optional0
type.sendNotification1
type.trackQuest1
type.versionInitial
type.path.editorPathempty string
type.path.fileEntryIndex2

Then set each typed path:

IDtype.path.classNametype.path.realPath
11gameJournalQuestquests/minor_quest/cqa001
12gameJournalQuestObjectivequests/minor_quest/cqa001/cqa001_01/cqa001_01_obj_wait
14gameJournalQuestObjectivequests/minor_quest/cqa001/cqa001_01/cqa001_01_obj_wait
16gameJournalQuestquests/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 pathValue
condition.type.hours0
condition.type.minutes0
condition.type.seconds10
condition.type.miliseconds0

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 pathValue
type.factNamecqa001_completed
type.setExactValue1
type.value1

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:

SourceDestinationMeaning
0.Out10.InEnter the one-shot guard
10.True11.ActiveFirst run: activate and track the quest
10.False1.InAlready complete: terminate immediately
11.Out12.ActiveActivate and track the objective
12.Out13.InBegin the realtime wait
13.Out14.SucceededSucceed the objective after the wait
14.Out15.InContinue to the completion fact write
15.Out16.SucceededSucceed the quest after the fact write
16.Out1.InTerminate the successful route

Socket inventory matters even when a socket is unused:

Node IDsSupplied socketsLab 1 use
0CutDestination, OutOut is connected; cut is unwired
1CutDestination, Inboth normal routes enter In; cut is unwired
10CutDestination, In, False, Trueall except cut are connected
11, 12, 14, 16CutDestination, Active, Inactive, Succeeded, Failed, Outthe connected state input differs by node; Out continues; all other state inputs and cut are unwired
13CutDestination, In, Outnormal wait path is connected; cut is unwired
15CutDestination, In, Outnormal 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:

  1. the three depot paths and ArchiveXL filename match;
  2. the journal has the six exact types and IDs in one chain;
  3. both journal localization keys resolve to the two secondary keys;
  4. the questphase has IDs 0, 1, and 10–16, with no extra nodes;
  5. the nine connections match the socket table above;
  6. both outer questphase arrays are empty;
  7. the condition, delay, fact-write, journal path, notification, and tracking values match the property tables;
  8. 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:

  1. run Project > Scan for broken file references;
  2. run Project > Scan for broken files;
  3. run Project > Run File Validation on the entire project;
  4. resolve every validation error owned by this project before continuing;
  5. close Cyberpunk 2077;
  6. choose Build > Install and wait for packing and installation to finish;
  7. 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

SymptomInspect first
No quest activationInstalled .archive.xl, its quest.phases path, ArchiveXL log, and the save’s completion fact
Quest title or objective text is blankJournal localization key versus onscreen secondaryKey
Objective stays activeID 13.Out to ID 14.Succeeded, not 14.Active
Quest finishes but repeatsID 15 exact fact write and the fact value on the reloaded save
First run works; replay looks inconsistentJournal state and other save-backed data left by the earlier run
WolvenKit graph looks different after reopenCompare 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.

RecordValue
Target environmentCyberpunk 2077 2.31a, WolvenKit 8.19.0, ArchiveXL 1.27.0, RED4ext 1.30.0, redscript 0.5.31
Supplied resource statusStructurally validated
In-game resultRecord it while following the save matrix below
Runtime test dateNot 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:

  1. Exit the game completely.
  2. Download and extract the completed Lab 1 checkpoint into a writable projects directory outside the game.
  3. Open CQA_Lab01_OneShot.cpmodproj in WolvenKit.
  4. Confirm the project contains the three cooked resources under source\archive\mod\cqa\cqa001 and source\resources\CQA_Lab01_OneShot.archive.xl.
  5. Use the normal Install action. Do not choose Install as REDmod and do not use hot reload for this acceptance run.
  6. 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.

CaseStarting stateExpected observationRequired record
First loadUntouched pre-mod saveQuest and objective activate once; after about ten real-time seconds the objective and quest succeedStart save identity, visible state/timing, logs
Mid-flow reloadNew save made while the objective is activeReload reaches a defined, non-duplicated state rather than silently creating a second runSave identity, state before/after reload, logs
Completed reloadNew save made after successRoot guard takes the already-complete route; no reactivationCompletion state before exit and after reload
Reinstall same buildCompleted save; the same archive and .archive.xl reinstalledReinstalling bytes does not erase save-backed completion or reactivate the questInstalled filenames and observed state
Clean replayOriginal untouched pre-mod saveThe same first-run route remains reproducibleOriginal 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:

  1. Project-output reset: WolvenKit’s Clean All removes the generated project packed directory. It does not remove installed game files, source resources, or save state.
  2. Installation reset: with the game closed, remove only CQA_Lab01_OneShot.archive and CQA_Lab01_OneShot.archive.xl from <Cyberpunk 2077>\archive\pc\mod. Preserve them with the test record first if you need to reproduce a problem. Do not delete the whole mod directory.
  3. State reset: load the preserved manual save that predates the first cqa001 installation. 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

SymptomFirst boundary to check
WolvenKit cannot packProject source paths and WolvenKit Log panel
.archive exists but .archive.xl does notsource\resources staging
ArchiveXL log is absentRED4ext/plugin installation
Registration reports a missing phaseExact archive depot path versus .archive.xl path
Quest appears without textJournal/localization merge and localization keys
Quest works only on one old savePersistent fact, journal, or checkpoint state
Completed quest runs againGuard 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:

  1. Resource anatomy identifies the CR2W root, graph, nodes, sockets, external references, and dependency lists.
  2. Registering a root separates ArchiveXL root attachment from normal depot-resource resolution.
  3. Inputs and outputs separates a phase interface from the sockets used inside its graph.
  4. Calling child phases documents questPhaseNodeDefinition and the parent/child handoff.
  5. Prefab dependencies explains root-owned prefab scope, phasePrefabs, and phaseInstancePrefabs without inventing a universal inheritance rule.
  6. Completion and interruption reviews terminating outputs, parent continuation, re-entry, and cut obligations.
  7. Complex cleanup, interruption, and cancellation expands those obligations into an owner ledger for scenes, communities, AI, devices, markers, vehicles, monitors, and durable state.
  8. 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:

ConcernAuthoritative ownerQuestphase role
Quest and objective hierarchyJournal resourceChange entry state through a typed journal path
Player-facing textLocalization resourceTrigger the state that presents it
Dialogue and cinematic timingScene resourceLaunch the scene and receive an outcome
Community, trigger, or markerWorld resourceRefer to it through a NodeRef and manage its lifecycle
Durable progressionSave-backed fact or journal stateRead 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: []
PropertySupported interpretation
cookingPlatformTarget cooking platform recorded by the resource
graphHandle to the executable questGraphDefinition
inplacePhasesStorage associated with inline/in-place phases; empty in Labs 1–5
phasePrefabsQuest-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 questSocketDefinition objects;
  • 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 typeDecisive payload
questInputNodeDefinitionExposed phase input name
questOutputNodeDefinitionExposed phase output name and output type
questConditionNodeDefinitionImmediate completion-fact comparison
questJournalNodeDefinitionJournal entry type, path, and incoming state socket
questPauseConditionNodeDefinitionRealtime delay or state-shaped trigger condition
questMappinManagerNodeDefinitionJournal map-pin path and active/inactive operation
questFactsDBManagerNodeDefinitionExact fact write operation
questPhaseNodeDefinitionExternal 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:

  1. Confirm RootChunk.$type is questQuestPhaseResource.
  2. Confirm graph.Data.$type is questGraphDefinition.
  3. Inventory every node by graph-local id and RED type.
  4. Record each connected source socket and destination socket.
  5. Inspect only the properties decisive for that node’s behavior.
  6. Review inplacePhases, root phasePrefabs, and every phase node’s phaseInstancePrefabs separately.
  7. For an external child, confirm phaseGraph is null and the soft phaseResource path resolves to an archived CR2W file.
  8. Match the parent phase-node interface to child input/output node names.
  9. 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
EdgeOwnerWhat failure looks like
Root attachmentLoose .archive.xlRoot never becomes reachable or ArchiveXL logs registration error
Child resolutionParent questPhaseNodeDefinition plus packed archiveParent 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:

RegistrationMakes available
quest.phasesExecutable root questphase
journalJournal tree and entries
localization.onscreensResolved player-facing strings
streaming.blocksWorld 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 ownsChild owns
Completion guard and first-run activationReach/leave activity
Root prefab declarationDirect trigger and marker references
Starting the childReturning Out1
Post-handoff confirmation and final completionLocal 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:

  1. Confirm the .archive.xl registers only the exact root depot path.
  2. Confirm the packed archive contains both root and child at the manifest paths.
  3. Confirm the parent phaseResource.DepotPath exactly matches the child.
  4. Confirm the loose .archive.xl is installed beside the archive as required by ArchiveXL.
  5. Inspect ArchiveXL and RED4ext logs for registration and resource errors.
  6. Run from a save whose relevant facts and journal state are known.
  7. Observe child entry, child Out1, and the first parent-only operation after the handoff.
  8. 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/nodeGraph IDInterface valueInternal or parent socket
Child questInputNodeDefinition0socketName: In1emits internal Out
Parent questPhaseNodeDefinition13input In1receives parent flow
Child questOutputNodeDefinition1socketName: Out1, type: Terminatingreceives internal In
Parent questPhaseNodeDefinition13output Out1emits 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:

  1. a reachable child output node with the intended socketName;
  2. a same-named output socket on the invoking phase node;
  3. an intentional parent connection or documented terminal policy;
  4. 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:

QuestionWhy 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
PropertySupported interpretation
phaseResourceSoft depot reference to the external child resource
phaseGraphInline graph slot; null in this external-child shape
phaseInstancePrefabsPrefab list on this phase-node instance; empty here
saveLockExact serialized value 0; broader behavior is not generalized
unfreezingTriggerNodeRefExact 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 ownsBoundary child owns
Completion guardReach and leave objective states
Quest and phase activationCheckpoint pin activation/inactivation
Root phasePrefabs declarationDirect #cqa004_tr_reach and #cqa004_tr_leave use
Starting the childWaiting for current inside/outside state
Interpreting returned Out1Returning Out1 after leave succeeds
30-second handoff confirmationNo post-return work
Completion fact and final quest stateNo 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:

  1. Confirm the parent phaseResource.DepotPath matches the archived child.
  2. Confirm the child is absent from ArchiveXL quest.phases.
  3. Confirm both roots are questQuestPhaseResource.
  4. Confirm external parent node phaseGraph is null and phaseInstancePrefabs is empty.
  5. Match every parent input and output name to a child interface node.
  6. Confirm each intended child route reaches an output.
  7. Confirm each returned parent output has a continuation.
  8. Inventory root and child prefab declarations separately.
  9. Confirm no ordinary edge uses CutDestination in the unwired lab.
  10. 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 locationLab 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:

  1. Inventory every non-zero NodeRef used by every phase resource.
  2. Group local #... refs by the full root that gives them context.
  3. Inventory each resource root’s phasePrefabs and each invoking node’s phaseInstancePrefabs separately.
  4. Identify the composition root and the intended prefab activation lifetime.
  5. Confirm the world-side resources bind the same full root and child refs.
  6. Record whether each external child declares, duplicates, or omits the root.
  7. Cite runtime evidence for the exact arrangement; do not infer a general inheritance rule from one success.
  8. 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.

ClaimEvidence class
The sockets exist with type CutDestinationStructurally validated
Similar sockets exist in extracted vanilla phasesObserved in vanilla
An unwired Lab 4 child cleans up correctly when externally interruptedExperimental
A wired cut propagates across this parent/child boundary and reaches a particular recovery stateExperimental

Do not draw a cut edge merely to make the graph look complete. Before wiring one, define:

  1. what work may be active when the cut arrives;
  2. which child, scene, or monitor receives interruption;
  3. which journal entries become inactive, failed, or resumable;
  4. which communities, markers, devices, and monitors are released;
  5. which durable fact distinguishes cut from normal completion;
  6. which child output tells the parent cleanup has finished;
  7. 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 boundaryExpected active owner in Lab 4
Before reaching checkpointChild, before IsInside resolves
Between reach and leave volumesChild, before IsOutside resolves
After child return, during 30-second confirmationParent
After final completionNo 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 classWhat produces itRequired durable distinction
Normal successAuthoritative activity resultSuccess or branch outcome, written before destructive teardown
Authored failureDeath, target loss, timeout, combat state, or explicit failure factFailure reason and retry/terminal policy
Player cancellationA supported prompt/UI/workspot/scene exitWhether the activity remains active, resets, or terminates
Scene interruption and returnScene interruption scenario plus quest wrapper outputsInterrupted is not completed; return and terminal interruption need separate handling
Graph cutquestCutControlNodeDefinition.CutSource targeting named CutDestination socketsWhich routes are cut, whether the cut is permanent, and which cleanup route now owns them
Streaming transitionPlayer leaves a streamed owner while graph/save state remains activeReacquire, remain inactive, or fail according to an explicit policy
Mod removal or upgradeArchive changes while save-backed state remainsMigration/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 concernAcquisition boundaryRelease question
Parallel Pause Condition or delaySplit from a main routeCan a late signal remain harmless, or must a tested cut target it?
Journal objective/description/mappinJournal or Mappin Manager state changeWhich terminal state is correct for each outcome?
ScenequestSceneNodeDefinition input and scene entryWhich named normal, interruption, return, or failure output proves release?
Community actor or vehicleSpawn Manager activation/phase actionAre scene, AI, workspot, combat, carry, or seat owners finished before deactivation?
Gameplay AI / follower roleAI manager commandWhich later command or owner transfer ends that role safely?
Device interactionController eligibility, player prompt, UI/workspot, or quest commandPreserve, restore, disconnect, or leave native state—which is evidenced?
World marker/triggerSector placement plus quest/journal activationRetire the pin/presentation; do not mistake streaming for graph cancellation
Player-vehicle recordPlayer-vehicle system operationIs the exact vehicle record and questEnablePlayerVehicle_NodeType context applicable?
Placed world vehicleSector, prefab, community, or sceneWhich of those owners, not a guessed player-vehicle command, releases it?
Inventory/rewardItem or reward manager writeIs the write before or after commitment, and can any route repeat it?
Durable factFacts DB write and saveDoes 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 permanent is 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 originCleanup owner to investigate
Community/spawn-set vehicleCommunity entry/phase and the actors/seats still using it
Placed world vehicleSector/prefab and any resource-specific quest operation
Player-vehicle recordPlayer-vehicle system and exact record state
Scene-staged vehicleScene 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:

  1. record the branch outcome;
  2. update the objective, description, or pin whose promise is now resolved;
  3. run safe resource teardown;
  4. return through the cleanup owner’s output;
  5. write the terminal one-shot fact and quest state at the owning parent;
  6. 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

  1. Duplicate a mod-owned test project and assign a fresh candidate identity. Do not edit an installed lab or copy a complete vanilla phase.
  2. 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.
  3. Give success, failure, cancel, timeout, and interruption distinct incoming routes. Write their discriminator before shared cleanup.
  4. 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.
  5. Wire scene normal and supported interruption outputs explicitly. Leave unsupported Default INT, Default RET, and CutDestination routes visibly unwired and label them Experimental.
  6. Release AI, workspot, carry, and seat ownership before community deactivation. Use a measured safe boundary where visible disappearance matters.
  7. Retire objectives and mappins according to each outcome. Do not let common cleanup overwrite a deliberate Failed, Succeeded, or resumable state.
  8. Apply device and vehicle operations only to the exact controller/record and owner class for which the experiment has evidence.
  9. 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.
  10. 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.
  11. 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.

CaseRequired observation
Normal successOne outcome is written; presentation retires; every owner releases in order; terminal state occurs once
Each authored failureFailure reason survives cleanup and reload; no success reward, item removal, or objective state leaks in
Player cancellationRetry or terminal-cancel policy is exact; UI/workspot/scene state releases; no irreversible success write occurs
Monitor winsLosing main-route signal cannot mutate state later
Main route winsLosing monitor remains harmless or the tested cut handles it
Near-simultaneous signalsThe observed result matches the only ordering on which the design relies
Save with all listeners armedReload restores or neutralizes each route without duplicate outcome writes
Save after outcome, before teardownReload resumes the correct cleanup branch and does not re-run activity success
Save after each owner releaseRemaining owners finish; already released resources do not duplicate, vanish incorrectly, or regain ownership
Active scene interruption/returnSupported path retains actor and progression; unsupported path remains recorded as Experimental
Combat/death during cleanupActor, AI, wave, and deactivation policy reaches an intentional result
Vehicle occupied during cleanupOccupants, seats, player control, and vehicle lifetime remain coherent
Device operation active during cleanupUI/personal link/workspot and controller reach the authored final state
Stream away and returnNo stale pin, duplicate spawn, restarted scene, repeated reward, or rearmed terminal listener
Completed-save reloadRoot guard bypasses activity; cleanup does not replay; world/save state matches the claim
Upgrade/removal copyLingering save-backed state is documented and not misreported as clean-install behavior
Second untouched saveThe 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

SymptomInspect first
Success and failure both writeOutcome discriminator placement, late listener gates, and convergence before state writes
Quest ends but marker/actor/vehicle remainsPhase output occurred before owner-specific teardown or targeted the wrong owner class
Actor disappears during dialogue/combatCommunity deactivation precedes scene/AI/workspot/combat release
Scene ends but quest does not continueNamed exit, quest-wrapper output, child socket, and parent continuation are separate boundaries
Reload repeats reward or item removalCommitment write is reachable twice or terminal/re-entry guard is too late
Reload skips required cleanupTerminal fact was written before cleanup handoff or active graph resumed on the bypass path
Cut route produces an unexpected ordinary outputThe design assumed unproved cut semantics; isolate and retain the exact target behavior
Vehicle disappears at another vehicle’s cleanupWrong acquisition owner or record domain; inspect cross-vehicle state and intermediate child handoff
Device returns to an old stateReused persistent identity or save-backed controller state, not merely the current archive payload
Stream return duplicates the encounterActivation/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.

RecordValue
Structural validation date2026-08-09
Runtime test dateNot 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

ComponentExact version
Cyberpunk 2077 for Windows (GOG)2.31a (public patch 2.31)
WolvenKit8.19.0
ArchiveXL1.27.0
RED4ext1.30.0
redscript0.5.31

Other versions may expose different editor wrappers or runtime behavior.

Prerequisites and downloads

Complete Lab 3, then read:

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

ResourceDepot pathOwns
Root questphasemod\cqa\cqa004\phases\cqa004.questphaseRoot graph, completion policy, external child call, and root prefab declaration
Boundary child questphasemod\cqa\cqa004\phases\cqa004_boundary.questphaseReach/leave activity and terminating Out1
Journalmod\cqa\cqa004\journal\cqa004.journalQuest, phase, three objectives, and handoff map pin
Onscreen localizationmod\cqa\cqa004\localization\en-us\onscreens\cqa004.jsonQuest title, three objective strings, and pin caption
Streaming blockmod\cqa\cqa004\world\cqa004_handoff.streamingblockQuest and AlwaysLoaded sector descriptors
Quest sectormod\cqa\cqa004\world\cqa004_handoff.streamingsectorReach and leave trigger definitions and placements
AlwaysLoaded sectormod\cqa\cqa004\world\cqa004_always_loaded.streamingsectorStatic handoff marker definition and placement
ArchiveXL registrationCQA_Lab04_HandoffPoint.archive.xlRoot 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:

OwnerNodePlacementGeometry or orientation
Quest sectorworldTriggerAreaNode reach(-1000.02, 1497.2208, 2.3)16-point regular outline, 25 m circumradius, 12 m high
Quest sectorworldTriggerAreaNode leave(-1000.02, 1497.2208, 0.3)20-point regular outline, 110 m circumradius, 16 m high
AlwaysLoaded sectorworldStaticMarkerNode(-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 IDRED typePurpose
0questInputNodeDefinitionExpose root In1
13questPhaseNodeDefinitionResolve child and match In1/Out1
1questOutputNodeDefinitionTerminate 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

IDRED node typeDecisive payloadPurpose
0questInputNodeDefinitioninterface In1Enter registered root
1questOutputNodeDefinitionterminating Out1End success and bypass routes
10questConditionNodeDefinitioncqa004_completed Equal 0Bypass a completed save
11questJournalNodeDefinitionquest pathActivate Handoff Point
12questJournalNodeDefinitionphase pathActivate phase
13questPhaseNodeDefinitionsoft child path; null inline graph; empty instance prefabsInvoke Boundary child through In1 and receive Out1
14questJournalNodeDefinitionconfirmation objectiveProve parent-only continuation became active
15questPauseConditionNodeDefinition30-second realtime delayKeep the post-return parent window observable
16questJournalNodeDefinitionconfirmation objectiveSucceed confirmation
17questJournalNodeDefinitionphase pathSucceed phase
18questFactsDBManagerNodeDefinitionset cqa004_completed = 1 exactlyPersist one-shot completion
19questJournalNodeDefinitionquest pathSucceed Handoff Point

Root edge contract

SourceDestinationMeaning
0.Out10.InEnter completion guard
10.False1.InCompleted-save bypass
10.True11.ActiveFirst-run activation
11.Out12.ActiveActivate phase
12.Out13.In1Enter external child
13.Out114.ActiveResume parent after normal child return
14.Out15.InBegin visible parent-only window
15.Out16.SucceededEnd confirmation wait
16.Out17.SucceededSucceed phase
17.Out18.InWrite durable completion
18.Out19.SucceededSucceed quest
19.Out1.InTerminate successful root route

Exact boundary child phase

IDRED node typeDecisive payloadPurpose
0questInputNodeDefinitioninterface In1Receive parent invocation
1questOutputNodeDefinitionterminating Out1Return normal completion
10questJournalNodeDefinitionreach objectiveActivate reach objective
11questMappinManagerNodeDefinitionpin path; disable previous 0Activate and track handoff pin
12questPauseConditionNodeDefinitionplayer IsInside #cqa004_tr_reachWait for current inside state
13questMappinManagerNodeDefinitionsame pin path; disable previous 0Inactivate handoff pin
14questJournalNodeDefinitionreach objectiveSucceed reach objective
15questJournalNodeDefinitionleave objectiveActivate leave objective
16questPauseConditionNodeDefinitionplayer IsOutside #cqa004_tr_leaveWait for current outside state
17questJournalNodeDefinitionleave objectiveSucceed leave objective before return

Child edge contract

SourceDestinationMeaning
0.Out10.ActiveBegin child-owned activity
10.Out11.ActiveActivate pin
11.Out12.InArm reach condition
12.Out13.InactiveRetire pin after reach
13.Out14.SucceededSucceed reach objective
14.Out15.ActiveActivate leave objective
15.Out16.InArm leave condition
16.Out17.SucceededSucceed leave objective
17.Out1.InTerminate 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

SymptomCheck firstBoundary
Quest never appearsRoot path in .archive.xl, installed archive pair, ArchiveXL/RED4ext logsThe child is not an additional root registration
Quest activates but child work never startsNode 13 soft path, archived child, parent In1, child socketName: In1A valid parent CR2W does not prove external resolution
Child finishes but confirmation never appearsChild output reachability, child socketName: Out1, parent 13.Out1 -> 14.ActiveJournal success is not implicit parent continuation
Trigger refs fail only in childRoot prefab entry, empty child/instance lists, full world binding, exact installed buildDo not “fix” by duplicating until the tested arrangement is diagnosed
Reload restarts or skips workSave history, active owner at save time, fact write order, journal stateA console fact reset is not a clean save
An interruption route behaves unpredictablyConfirm every CutDestination remains unwiredLab 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.

RecordValue
Structural validation date2026-08-09
Runtime test dateNot yet recorded

Required environment

ComponentExact version
Cyberpunk 2077 for Windows (GOG)2.31a (public patch 2.31)
WolvenKit8.19.0
ArchiveXL1.27.0
RED4ext1.30.0
redscript0.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

  1. Download and extract the start ZIP.
  2. Open CQA_Lab04_HandoffPoint_Start.cpmodproj with File > Open Project.
  3. Make an untouched backup of the extracted directory.
  4. Confirm source\archive and source\raw each contain the seven depot resources listed in the overview.
  5. Confirm CQA_Lab04_HandoffPoint_Start.archive.xl is under source\resources.
  6. 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:

ResourceCheck
cqa004.questphaseRegistered root, one phasePrefabs entry, external phase node
cqa004_boundary.questphaseUnregistered external child, empty phasePrefabs
cqa004.journalQuest, phase, reach, leave, confirmation, and map-pin entries
cqa004.jsonFive exact secondary keys and English strings
cqa004_handoff.streamingblockQuest-sector and AlwaysLoaded-sector descriptors
cqa004_handoff.streamingsectorTwo trigger nodes, placements, refs, and outlines
cqa004_always_loaded.streamingsectorStatic 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 keyEnglish value
cqa_cqa004_titleHandoff Point
cqa_cqa004_objective_reachReach the handoff point.
cqa_cqa004_objective_leaveClear the handoff area.
cqa_cqa004_objective_confirmWait for handoff confirmation.
cqa_cqa004_mappin_handoffHandoff 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:

PropertyExact value
RED typequestPhaseNodeDefinition
phaseGraphnull
phaseInstancePrefabsempty
phaseResource.DepotPathmod\cqa\cqa004\phases\cqa004_boundary.questphase
phaseResource.FlagsSoft
saveLock0
unfreezingTriggerNodeRefzero NodeRef
interface socketsinput In1, output Out1
interruption socketCutDestination, 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 IDAdd Node labelRED type
10ConditionquestConditionNodeDefinition
11JournalquestJournalNodeDefinition
12JournalquestJournalNodeDefinition
14JournalquestJournalNodeDefinition
15PauseConditionquestPauseConditionNodeDefinition
16JournalquestJournalNodeDefinition
17JournalquestJournalNodeDefinition
18FactsDBManagerquestFactsDBManagerNodeDefinition
19JournalquestJournalNodeDefinition

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:

PropertyValue
factNamecqa004_completed
comparisonTypeEqual
value0

At parent ID 18, choose questSetVar_NodeType:

PropertyValue
factNamecqa004_completed
setExactValue1
value1

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:

PropertyValue
optional0
sendNotification1
trackQuest1
versionInitial
path.editorPathempty
path.fileEntryIndex2
IDsclassNamerealPathConnected state
11, 19gameJournalQuestquests/minor_quest/cqa00411.Active, 19.Succeeded
12, 17gameJournalQuestPhasequests/minor_quest/cqa004/cqa004_0112.Active, 17.Succeeded
14, 16gameJournalQuestObjectivequests/minor_quest/cqa004/cqa004_01/cqa004_01_obj_confirm14.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:

PropertyValue
hours0
minutes0
seconds30
miliseconds0

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:

SourceDestination
0.Out10.In
10.False1.In
10.True11.Active
11.Out12.Active
12.Out13.In1
13.Out114.Active
14.Out15.In
15.Out16.Succeeded
16.Out17.Succeeded
17.Out18.In
18.Out19.Succeeded
19.Out1.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 IDAdd Node labelRED type
10JournalquestJournalNodeDefinition
11MappinManagerquestMappinManagerNodeDefinition
12PauseConditionquestPauseConditionNodeDefinition
13MappinManagerquestMappinManagerNodeDefinition
14JournalquestJournalNodeDefinition
15JournalquestJournalNodeDefinition
16PauseConditionquestPauseConditionNodeDefinition
17JournalquestJournalNodeDefinition

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:

IDsclassNamerealPathConnected state
10, 14gameJournalQuestObjectivequests/minor_quest/cqa004/cqa004_01/cqa004_01_obj_reach10.Active, 14.Succeeded
15, 17gameJournalQuestObjectivequests/minor_quest/cqa004/cqa004_01/cqa004_01_obj_leave15.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:

PropertyValue
path.classNamegameJournalQuestMapPin
path.realPathquests/minor_quest/cqa004/cqa004_01/cqa004_01_obj_reach/cqa004_01_qmp_handoff
path.fileEntryIndex2
path.editorPathempty
disablePreviousMappins0

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:

IDcondition.typetriggerAreaRef
12IsInside#cqa004_tr_reach
16IsOutside#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:

SourceDestination
0.Out10.Active
10.Out11.Active
11.Out12.In
12.Out13.Inactive
13.Out14.Succeeded
14.Out15.Active
15.Out16.In
16.Out17.Succeeded
17.Out1.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:

  1. parent node 13.In1 matches child input socketName: In1;
  2. child output socketName: Out1 is terminating;
  3. parent node 13.Out1 connects only to parent 14.Active;
  4. phaseResource is soft and matches the archived child path exactly;
  5. phaseGraph is null;
  6. parent phase-node instance prefabs and child root prefabs are empty;
  7. only the root declares #cqa004_pr_handoff;
  8. no edge uses CutDestination;
  9. 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:

  1. run Project > Scan for broken file references;
  2. run Project > Scan for broken files;
  3. run Project > Run File Validation on the entire project;
  4. resolve all project-owned errors;
  5. close Cyberpunk 2077;
  6. choose Build > Install and wait for packing and installation;
  7. 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

SymptomCheck
Parent activates but child does notExact soft depot path, packed child, null phaseGraph, matching In1, and logs
Child finishes but confirmation does not startChild output reachability, terminating Out1, and 13.Out1 -> 14.Active
Trigger refs fail after the splitParent root prefab entry, empty child/instance lists, world full root, and exact child refs
Both phases start independentlyRemove the child from ArchiveXL quest.phases; only the root is registered
Completion bypass runs too earlyConfirm guard is Equal 0 and fact write occurs at parent ID 18
Cut seems to terminate normallyRemove the guessed cut edge; Lab 4 leaves every cut socket unwired
Graph changes after reopenRenumber through temporary IDs, save/reopen before wiring, then compare semantics
Canonical hashes do not matchUse 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.

RecordValue
Structural validation date2026-08-09
Runtime test dateNot 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

ComponentExact version
Cyberpunk 2077 for Windows (GOG)2.31a (public patch 2.31)
WolvenKit8.19.0
ArchiveXL1.27.0
RED4ext1.30.0
redscript0.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:

  1. choose a manual save made at Allen Street with no history of any Lab 4 candidate;
  2. confirm the player is outside the 25-metre reach volume and can approach the cabinet row by the verified ordinary route;
  3. copy the complete save-slot directory into your test folder;
  4. record its in-game label and slot-directory name;
  5. confirm that this save predates the first Lab 4 install;
  6. preserve that original byte-for-byte for both clean-walk and clean-replay;
  7. derive separate active-child saves before reach and between boundaries;
  8. derive a separate parent-active save during the 30-second confirmation;
  9. 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
  1. Close the game.

  2. Build and install through WolvenKit.

  3. Confirm only this candidate’s pair is installed:

    archive\pc\mod\CQA_Lab04_HandoffPoint.archive
    archive\pc\mod\CQA_Lab04_HandoffPoint.archive.xl
    
  4. Confirm the archive contains all seven depot resources, including the child and both sectors reached through the block.

  5. 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.phases entry, for cqa004.questphase;
  • no independent entry for cqa004_boundary.questphase;
  • the journal, onscreen localization, and streaming block entries;
  • no cqa004 error 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:

  1. Wait for handoff confirmation. becomes active once;
  2. the reach and leave sequence does not restart;
  3. after 30 realtime seconds, confirmation and phase succeed;
  4. the completion-fact write is reached before final quest success;
  5. 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.

  1. Load without changing the installed candidate.
  2. Confirm the reach objective and pin return once.
  3. Confirm leave and confirmation are not yet active.
  4. Enter reach, remain inside leave, then walk outside.
  5. Confirm the child returns once and the parent completes once.
  6. 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.

  1. Confirm reach remains succeeded and the handoff pin remains inactive.
  2. Confirm leave is active and confirmation is not active.
  3. Wait briefly without crossing; the child must not return yet.
  4. Walk outside the leave volume.
  5. Confirm leave succeeds, confirmation activates once, and completion follows once.
  6. 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.

  1. Confirm the confirmation objective is active.
  2. Confirm reach and leave do not reactivate and no pin returns.
  3. Observe whether the realtime delay resumes or restarts; record the measured result rather than assuming one.
  4. Confirm the parent succeeds confirmation, phase, fact, and quest once.
  5. 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.

  1. Travel by ordinary movement beyond the finite Quest descriptor box; do not use fast travel for this case.
  2. Remain away long enough to collect a clear before/away/return observation.
  3. Return by ordinary movement to the site.
  4. Confirm the reach objective and pin remain coherent.
  5. Cross reach and leave, then confirm one child return and one parent completion.
  6. 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 cqa004 error.

This is the completed-route bypass, not a second successful playthrough.

Run 7: completed reinstall

  1. Close the game.
  2. Remove only the two installed canonical files.
  3. Reinstall the preserved copies of the same two files.
  4. Load the same completed save.
  5. Confirm the quest remains completed and the child does not restart.
  6. Confirm ArchiveXL registers the root without a separate child entry.
  7. 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

ObservationClassification and next check
Parent registered, child path error in logFailed external resolution; verify packed path and soft phaseResource
Child appears to run twiceFailed lifecycle; verify clean save, one root registration, and no duplicate candidate
Trigger refs fail only from childFailed candidate; inspect root prefab, child/instance empty lists, full world binding, and hashes before changing declarations
Leave succeeds but confirmation never activatesFailed handoff; inspect child output and parent 13.Out1 -> 14.Active
Reload before reach restarts journal/pinFailed active-child recovery, even if later completion succeeds
Post-return reload restarts childFailed owner transition; retain evidence before editing the graph
Completed reinstall starts child againFailed one-shot persistence or candidate identity
A cut path is untestedExpected scope boundary; do not mark cut runtime-proven
One run is missing its logsRepeat 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 True or False;
  • 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:

LayerNative examplesQuestion
Evaluation schedulequestConditionNodeDefinition, questPauseConditionNodeDefinitionIs the predicate sampled on entry, or does this path wait?
PredicatequestFactsDBCondition, questTriggerCondition, questJournalConditionWhat piece of game state counts as fulfilled?
Predicate compositionquestLogicalCondition containing child conditionsMust 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 behaviorStarting shapeEssential caution
Decide between two routes using current stateImmediate condition nodeFalse is an output route, not a request to wait
Continue this route after a predicate is fulfilledPause condition nodeDo not assume how it polls, saves, cancels, or re-arms
Require several state predicates togetherLogical condition tree beneath either nodeThis combines truth values, not execution signals
Prevent a one-shot effect from running againImmediate fact guard plus an explicit persistent fact writeThe save may already contain either value
Watch for failure while other work continuesA separately activated waiting pathStarting a listener does not define how it is stopped
Accept one of several arriving graph signalsGraph topology, not a nested condition treeWinner, 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

ChapterQuestion it answers
Immediate branches and waiting gatesWhen does a condition run, and which sockets continue the graph?
Condition payloadsHow do fact, trigger, distance, journal, and time predicates represent state?
Boolean condition treesHow are leaf predicates nested under AND or OR, and why is there no generic NOT recipe?
Signal flowHow do graph-level AND, XOR, Hub, and Switch shapes route execution signals?
Delays and persistenceWhich clock and save-state questions must a delayed route answer?
Parallel monitors and cancellationHow can a side listener remain harmless or be explicitly cut?
Condition-family catalogWhich additional native condition families were observed, and what still needs research?
Lab 2: Signal RaceHow do immediate selection, parallel waits, a Boolean AND, and XOR-shaped convergence fit in one project?
Author Signal RaceWhich native properties and 22 socket edges reproduce the supplied graph?
Test both routesHow 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:

  1. In the Asset Browser, locate the exact depot path named by a chapter.
  2. Open it without adding it to a distributable project, or add it only to a separate local inspection project.
  3. Record the graph-local node ID, concrete node class, condition payload, socket names, and focused connected edges.
  4. Record the game version, WolvenKit version, extraction date, and optionally a local hash.
  5. Keep the extracted .questphase and 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:

PartRole
conditionHandle to the leaf or logical predicate
InOrdinary execution entry
TrueRoute selected when the predicate is fulfilled
FalseRoute selected when the predicate is not fulfilled
CutDestinationSeparate 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:

PartRole
conditionHandle to the leaf or logical predicate
InStarts this wait
OutContinues after fulfillment
CutDestinationTarget 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.

  1. Decide the behavior in plain language: branch now or wait until.
  2. Add questConditionNodeDefinition for the first behavior, or questPauseConditionNodeDefinition for the second.
  3. In the node’s condition handle, create the wrapper that owns the intended state domain. For a fact comparison, that is questFactsDBCondition with a questVarComparison_ConditionType beneath type.
  4. Set every decisive payload property. Do not leave a default fact name, journal path, NodeRef, comparison, or threshold unexplained.
  5. Show unused sockets if necessary. Connect In, then both True and False for an immediate branch, or connect In and Out for a waiting gate.
  6. Leave CutDestination unwired unless the activity has a designed and tested interruption owner.
  7. 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:

  1. an untouched save made before the mod was installed;
  2. a save while the waiter is active;
  3. a save after the releasing fact changed;
  4. a completed save after reinstalling the identical build;
  5. 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:

  1. Confirm the concrete node class: immediate Condition versus Pause Condition.
  2. Trace the edge into In and every expected outgoing socket by node ID.
  3. Inspect the concrete payload and its comparison instead of relying on the node caption.
  4. Check the fact, journal path, NodeRef, or timer fields for the test save and installed build.
  5. Determine whether the path was started once, more than once, or never.
  6. Check whether a competing route completed without stopping this waiter.
  7. 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:

comparisonTypePredicate
Equalfact value equals authored value
NotEqualfact value does not equal authored value
Greaterfact value is greater than authored value
GreaterOrEqualfact value is at least authored value
Lessfact value is less than authored value
LessOrEqualfact 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:

  1. Create a questFactsDBCondition handle.
  2. Create questVarComparison_ConditionType beneath its type property.
  3. Set the exact factName, signed integer value, and comparisonType.
  4. Reopen the resource and verify both handles are non-null and retain their concrete classes.
  5. 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:

PropertyRole
typeTrigger predicate such as IsInside or IsOutside
triggerAreaRefNodeRef of the authored trigger area
activatorRefEntity reference used when an explicit activator is required
isPlayerActivatorSelects 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

  1. Establish ownership of the mod’s trigger and its NodeRef before editing the questphase.
  2. Create questTriggerCondition beneath the graph node’s condition handle.
  3. Set triggerAreaRef and the intended type explicitly.
  4. For the player form, set isPlayerActivator deliberately and inspect the resulting activatorRef; for another actor, begin from a separately verified actor-reference shape.
  5. 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:

TypeDecisive properties
questObjectDistanceentityRef, nodeRef2
questValueDistancedistanceValue

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 questJournalConditionDecisive fieldsQuestion
questJournalEntryState_ConditionTypepath, engine state, invertedIs the entry in an authored lifecycle state, optionally inverted?
questJournalEntry_ConditionTypepath, user stateIs the entry in an authored user-facing state?
questJournalEntryVisited_ConditionTypepath, visitedDoes 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

  1. Identify the exact journal entry and the state dimension being tested: lifecycle, user state, or visited.
  2. Create questJournalCondition, then choose the matching concrete nested type.
  3. Populate the full typed path. Verify className against the leaf resource, not against the surrounding quest or folder.
  4. Set state, visited, or inverted only where that concrete type owns the property.
  5. 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 typeFieldsRetained count
questRealtimeDelay_ConditionTypehours, minutes, seconds, miliseconds146
questGameTimeDelay_ConditionTypedays, hours, minutes, seconds19

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 71 in base\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 146 in base\quest\side_quests\sq011\phases\sq011_concert.questphase;
  • a three-day delay at node 61 and a five-hour delay at node 87 in base\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

  1. For the delay recipes covered here, put questTimeCondition beneath questPauseConditionNodeDefinition. The retained immediate time condition uses a different questTimePeriod_ConditionType inside an AND tree; that snapshot time-window shape is outside this recipe.
  2. Create questTimeCondition and the intended real-time or game-time nested type.
  3. Set every time component explicitly, including zero-valued components.
  4. Verify the exact concrete type and native property spelling after save and round-trip.
  5. 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:

CheckWhat to capture
Surrounding nodeConcrete immediate or pause node class and graph-local ID
WrapperConcrete class stored by the node’s condition handle
Nested payloadConcrete type class, if that wrapper owns one
Decisive propertiesComparison, value, path, NodeRef, activator, or time fields
Socket routeEntry socket and every connected result socket
External ownerFact 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 immediate True or False route;
  • beneath questPauseConditionNodeDefinition, the combined predicate defines when the waiting path may continue through Out.

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 layerExecution-signal layer
questLogicalConditiongraph nodes such as questLogicalAndNodeDefinition
Owns an operation and child conditionsOwns graph sockets and receives edges
Combines truth valuesCombines or routes arriving execution signals
Nested in one node’s condition handleListed 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:

DomainFocused negative formBoundary
Fact comparisoncomparisonType: NotEqualNegates that integer equality, not an arbitrary subtree
Trigger statetype: IsOutsideDescribes that trigger predicate; exact transition timing still needs tests
Journal lifecycle statequestJournalEntryState_ConditionType.inverted: 1Belongs 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:

  1. Choose the surrounding immediate or pause node from the intended schedule.
  2. Set that node’s condition handle to questLogicalCondition.
  3. Set operation to the supported operation required by the design; begin with AND or OR.
  4. Add one handle per child to conditions. Each child must contain a concrete condition wrapper such as questFactsDBCondition or questTriggerCondition.
  5. To nest another group, store another questLogicalCondition as that child handle’s concrete object, set its operation, and populate its own conditions array.
  6. Inspect every leaf’s decisive properties and every nested handle before leaving the property editor.
  7. 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:

DepthObjectDecisive value
0questLogicalConditionoperation: AND, two child handles
1questTriggerConditionplayer IsInside the named trigger NodeRef
1questLogicalConditionoperation: OR, three child handles
2three questFactsDBCondition objectsnamed 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 condition handle resolves;
  • every item in conditions is 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 In and 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 typeRole
InputAccepts an ordinary execution signal
OutputEmits an ordinary execution signal
CutSourceConnects a cut-control route to a target
CutDestinationReceives 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 typeCountSerialized ordinary-socket shapes
questLogicalAndNodeDefinition472–11 inputs, one output
questLogicalXorNodeDefinition512–4 inputs, one output
questLogicalHubNodeDefinition951→1/2/3 and 2/3→1
questSwitchNodeDefinition1one 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:

PropertyMeaning established by structure
inputSocketCountNumber of ordinary input sockets
outputSocketCountNumber of ordinary output sockets
socketsThe 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.questphase has two inputs and one output. Its Out1 itself has two outgoing edges.
  • In the same phase, Hub 195 is formally one-input/one-output, while two edges target that single In1 socket.

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 partObserved relationship
conditions[n].socketIdNumeric identity for that condition’s case
Case<socketId>Output socket associated with the item
InOrdinary input that reaches the switch
OtherwiseAdditional 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

  1. Decide whether you are combining predicates or execution routes. If they are predicates, return to Boolean condition trees.
  2. Add the graph node with the smallest required arity.
  3. Set inputSocketCount and outputSocketCount before wiring it.
  4. Confirm that the visible ordinary sockets match those counts.
  5. Connect each producer to the intended named input and the continuation to the intended output.
  6. Save, reopen, and inspect both endpoints of every edge. Do not count reciprocal handle references as separate connections.
  7. Export a focused review JSON and verify node IDs, socket names, arity, and resolved endpoints.
  8. 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

ComponentVersion
Cyberpunk 2077 for Windows2.31a (GOG)
WolvenKit8.19.0
ArchiveXL1.27.0
RED4ext1.30.0
redscript0.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:

PropertyRole
hoursWhole realtime hours
minutesWhole realtime minutes
secondsWhole realtime seconds
milisecondsMillisecond 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:

PropertyRole
daysWhole game-time days
hoursWhole game-time hours
minutesWhole game-time minutes
secondsWhole game-time seconds

Observed in vanilla: the following depot resources contain long game-time waits:

Depot pathObserved payload
base\quest\side_quests\sq011\phases\sq011_concert.questphase23 hours
base\quest\side_quests\sq011\phases\sq011_follow_up.questphase3 days
same follow-up phase5 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:

InterruptionRealtime questionGame-time question
Pause menuDoes wall-clock progress continue?Does game time stop?
Time skipIs the wait unaffected?Can the target be crossed immediately?
Fast travelDoes loading count?Does travel advance the target?
Save/loadIs elapsed duration restored or restarted?Is the target timestamp restored?
Process restartIs 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:

  1. one untouched save created before the candidate was first installed;
  2. one save made while the delay or listener is active;
  3. one save made after completion;
  4. the exact installed archive and registration file;
  5. exact game/mod versions and fresh logs;
  6. observations and fresh logs for each run.

Never reuse the mid-flow or completed save as the clean replay baseline.

Minimum timer acceptance matrix

CaseWhat to record
UninterruptedEarliest and observed completion time
Pause menuWhether the target advances while paused
Save during waitRemaining time or restart behavior after reload
Process restartSame observation after closing the game completely
Time skipWhether a game-time target is crossed and emitted once
Fast travelLoading/travel effect on both clock and output
Completed reloadNo second journal activation or delayed output
Clean replaySame 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:

  1. Store the authoritative outcome in a fact.
  2. Gate every journal or world-state writer on that outcome.
  3. Make repeated or losing signals bypass the writer.
  4. Terminate the containing route cleanly.
  5. 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:

  1. Identify the exact listener nodes whose lifecycle must end.
  2. Connect a Cut Control’s CutSource to each target’s CutDestination.
  3. Decide and record the intended permanent setting.
  4. Test both outcomes, both arrival orders, and simultaneous completion.
  5. Save with every target active, then reload and repeat.
  6. 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 activeRequired decision
Pause ConditionRemain harmless, satisfy, or explicitly cut
Realtime/game-time delayIgnore late output, gate it, or cut after testing
SceneMap every exit and interruption outcome
Community/world spawnDespawn or transfer ownership deliberately
Device persistent-state writeDefine 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:

CaseRequired observation
Main route winsOptional outcome and final quest state are correct
Monitor winsFailure/escalation outcome and final quest state are correct
Near-simultaneous signalsExactly the behavior the design relies on
Late losing signalNo second or contradictory journal/world write
Save with both armedReload completes or restores without duplication
Completed-save reloadNo listener reactivation
Clean replayBoth 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 wrapperConcrete nested payload or direct fieldRetained instances
questFactsDBConditionquestVarComparison_ConditionType259 / 259
questLogicalConditiondirect operation field87
questTimeConditiontime payload handle168
questJournalConditionjournal payload handle88

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:

WrapperConcrete payloadRetained instances
questTimeConditionquestRealtimeDelay_ConditionType146
questTimeConditionquestGameTimeDelay_ConditionType19
questTimeConditionquestTimePeriod_ConditionType2
questTimeConditionquestTickDelay_ConditionType1
questJournalConditionquestJournalEntryState_ConditionType66
questJournalConditionquestJournalEntry_ConditionType21
questJournalConditionquestJournalEntryVisited_ConditionType1

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 wrapperConcrete nested payload or direct fieldRetained instances
questTriggerConditiondirect type field214
questDistanceConditionquestDistanceComparison_ConditionType55 / 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 questObjectConditionRetained instances
questDevice_ConditionType44
questInventory_ConditionType11
questScan_ConditionType3
questDestruction_ConditionType2

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 questCharacterConditionRetained instances
questCharacterSpawned_ConditionType73
questCharacterMount_ConditionType67
questCharacterKilled_ConditionType37
questCharacterStatusEffect_CondtionType29
questCharacterState_ConditionType18
questCharacterCombat_ConditionType11
questCharacterHealth_ConditionType4
questCharacterHit_ConditionType4
questCharacterWorkspot_ConditionType4
questCharacterAttack_ConditionType1
questCharacterGender_CondtionType1
questCharacterQuickHacked_ConditionType1

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 wrapperConcrete payloadRetained instances
questSceneConditionquestSceneNode_ConditionType31 / 31
questContentConditionquestContentLock_ConditionType10 of 15
questContentConditionquestContentToken_ConditionType5 of 15
questSystemConditionquestPhoneMuted_ConditionType26 of 54
questSystemConditionquestCameraFocus_ConditionType24 of 54
questSystemConditionquestPhone_ConditionType4 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 wrapperConcrete payloadRetained instances
questVehicleConditionquestVehicleSpeed_ConditionType3 of 6
questVehicleConditionquestVehicleTrunk_ConditionType2 of 6
questVehicleConditionquestVehicleWater_ConditionType1 of 6
questSpawnerConditionquestSpawnerReady_ConditionType7 / 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 inspectingVanilla depot path
Facts, logical trees, triggers, time, journal, contentbase\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 systembase\quest\side_quests\sq011\phases\sq011_concert.questphase
Inventory, scan, scene, character, vehicle trunkbase\open_world\street_stories\heywood\vista_del_rey\sts_hey_rey_09\phases\sts_hey_rey_09_openworld.questphase
Device, killed, spawned, workspot, quickhackbase\open_world\street_stories\watson\little_china\sts_wat_lch_01\phase\sts_wat_lch_01_combat.questphase
Destruction, device, character combat, vehicle speedbase\open_world\street_stories\watson\northside_industrial_district\sts_wat_nid_03\phases\sts_wat_nid_03_openworld.questphase
Spawner ready, distance, character mountbase\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:

  1. The exact immediate or waiting graph node, its socket route, and the concrete condition wrapper stored in its handle.
  2. The nested payload class and every decisive field, including native enum values and exact spellings rather than inferred labels.
  3. 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.
  4. The producer of the observed state or event and the required activation order. A consumer condition does not create its source system.
  5. A reduced, mod-owned fixture that survives WolvenKit 8.19.0 save/reopen and CR2W round-trip checks with non-null handles and stable concrete types.
  6. 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.
  7. 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.

RecordValue
Structural validation date2026-08-09
Runtime test dateNot 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:

Use Cyberpunk 2077 2.31a, WolvenKit 8.19.0, ArchiveXL 1.27.0, RED4ext 1.30.0, and redscript 0.5.31.

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

ResourceDepot pathOwns
Root questphasemod\cqa\cqa002\phases\cqa002.questphaseGuard, selector, monitors, delays, fact writes, journal operations, convergence, termination
Journalmod\cqa\cqa002\journal\cqa002.journalQuest, phase, required objective, optional objective
Onscreen localizationmod\cqa\cqa002\localization\en-us\onscreens\cqa002.jsonThree English strings used by the journal
ArchiveXL registrationCQA_Lab02_SignalRace.archive.xlRoot 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 entryKeyEnglish text
cqa002 titlecqa_cqa002_titleSignal Race
cqa002_01_obj_wait descriptioncqa_cqa002_objective_waitWait for the signal test to resolve.
cqa002_01_obj_stable descriptioncqa_cqa002_objective_stableKeep 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.

IDRED node typeDecisive payloadPurpose
0questInputNodeDefinitioninterface In1Enter from the registered root
1questOutputNodeDefinitionTerminating, interface Out1End completed and bypass routes
10questConditionNodeDefinitioncqa002_completed Equal 0Branch immediately around a completed save
11questFactsDBManagerNodeDefinitionset cqa002_test_mode exactly 2Select the canonical variant; edit only this value for failure testing
12questJournalNodeDefinitionquest pathActivate and track Signal Race
13questJournalNodeDefinitionrequired-objective pathActivate the required objective
14questJournalNodeDefinitionoptional-objective pathActivate the optional objective and fan out its Out
15questPauseConditionNodeDefinitioncqa002_signal_failed Greater 0Wait for the failure result
16questPauseConditionNodeDefinitionAND of stop fact and mode 2Wait for the canonical stable result
17questConditionNodeDefinitioncqa002_test_mode Equal 1Choose one writer branch immediately
18questPauseConditionNodeDefinition30-second realtime delayDelay the edited failure writer
19questFactsDBManagerNodeDefinitionset cqa002_signal_failed exactly 1Fulfil the failure listener
20questPauseConditionNodeDefinition120-second realtime delayDelay the canonical stable writer and leave time for a mid-flow save
21questFactsDBManagerNodeDefinitionset cqa002_signal_stop exactly 1Fulfil the stable AND listener
22questJournalNodeDefinitionoptional objective, Failed inputRecord optional failure
23questJournalNodeDefinitionoptional objective, Succeeded inputRecord optional success
24questFactsDBManagerNodeDefinitionset cqa002_signal_succeeded exactly 1Persist the stable-route outcome
25questLogicalXorNodeDefinitiontwo inputs, one outputConverge the mutually exclusive outcome routes
26questJournalNodeDefinitionrequired objective, Succeeded inputComplete the required objective
27questFactsDBManagerNodeDefinitionset cqa002_completed exactly 1Persist the one-shot completion guard
28questJournalNodeDefinitionquest path, Succeeded inputComplete 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

SourceDestinationMeaning
0.Out10.InEnter guard
10.False1.InCompleted save bypass
10.True11.InFirst-run variant setup
11.Out12.ActiveActivate quest
12.Out13.ActiveActivate required objective
13.Out14.ActiveActivate optional objective
14.Out15.InArm failure listener
14.Out16.InArm stable listener
14.Out17.InEvaluate selector now
17.True18.InEdited mode: start failure delay
18.Out19.InWrite failure fact
17.False20.InCanonical mode: start stable delay
20.Out21.InWrite stop fact
15.Out22.FailedFail optional objective
22.Out25.In1Failure outcome reaches convergence
16.Out23.SucceededSucceed optional objective
23.Out24.InRecord stable success
24.Out25.In2Stable outcome reaches convergence
25.Out126.SucceededComplete required objective
26.Out27.InWrite completion fact
27.Out28.SucceededComplete quest
28.Out1.InTerminate 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

FactWriterReaderPurpose
cqa002_test_mode1116, 17Select and constrain the test variant
cqa002_signal_failed1915Failure monitor result
cqa002_signal_stop2116Stable monitor stop signal
cqa002_signal_succeeded24retained evidence/extension pointRecord stable optional success
cqa002_completed2710Bypass 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

SymptomCheck firstBoundary
Signal Race never appearsConfirm the .archive and .archive.xl names, all three registered depot paths, and the ArchiveXL/RED4ext logsRegistration or lookup failure; the graph has not yet been exercised
Both routes appear, or the wrong route winsConfirm 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 existA dirty save or extra writer invalidates route attribution
Optional objective never resolvesCheck the matching writer fact, its listener comparison, the delay payload, and the listener’s journal-state socketDo not infer XOR or cleanup behavior before the route reaches convergence
Quest completes twice after reloadVerify the 27 -> 28 order, node 10 completed guard, and a truly untouched versus completed saveA console fact reset is diagnosis, not clean-save evidence
Graph looks right but validation failsCompare node IDs, RED types, socket names, resolved edges, and decisive payloads—not canvas placement or handle IDsLayout 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.

RecordValue
Structural validation date2026-08-09
Runtime test dateNot 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

  1. Download and extract the start ZIP.
  2. Open CQA_Lab02_SignalRace_Start.cpmodproj through File > Open Project.
  3. Make an untouched backup of the extracted directory.
  4. Confirm the project contains three cooked resources beneath source\archive, their three review JSON files beneath source\raw, and CQA_Lab02_SignalRace_Start.archive.xl beneath source\resources.
  5. 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 cqa002 depot 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:

secondaryKeyfemaleVariant
cqa_cqa002_titleSignal Race
cqa_cqa002_objective_waitWait for the signal test to resolve.
cqa_cqa002_objective_stableKeep 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 IDAdd Node labelRED type
10ConditionquestConditionNodeDefinition
11FactsDBManagerquestFactsDBManagerNodeDefinition
12JournalquestJournalNodeDefinition
13JournalquestJournalNodeDefinition
14JournalquestJournalNodeDefinition
15PauseConditionquestPauseConditionNodeDefinition
16PauseConditionquestPauseConditionNodeDefinition
17ConditionquestConditionNodeDefinition
18PauseConditionquestPauseConditionNodeDefinition
19FactsDBManagerquestFactsDBManagerNodeDefinition
20PauseConditionquestPauseConditionNodeDefinition
21FactsDBManagerquestFactsDBManagerNodeDefinition
22JournalquestJournalNodeDefinition
23JournalquestJournalNodeDefinition
24FactsDBManagerquestFactsDBManagerNodeDefinition
25LogicalXorquestLogicalXorNodeDefinition
26JournalquestJournalNodeDefinition
27FactsDBManagerquestFactsDBManagerNodeDefinition
28JournalquestJournalNodeDefinition

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:

  1. move the added nodes to unique temporary IDs 100 through 118, preserving the table order;
  2. move those same nodes from the temporary IDs to target IDs 10 through 28 from 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.

IDfactNamecomparisonTypevalue
10cqa002_completedEqual0
17cqa002_test_modeEqual1

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:

IDtype.factNametype.value
11cqa002_test_mode2
19cqa002_signal_failed1
21cqa002_signal_stop1
24cqa002_signal_succeeded1
27cqa002_completed1

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:

PropertyValue
factNamecqa002_signal_failed
comparisonTypeGreater
value0

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:

ChildfactNamecomparisonTypevalue
0cqa002_signal_stopGreater0
1cqa002_test_modeEqual2

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:

PropertyValue
type.optional0
type.sendNotification1
type.trackQuest1
type.versionInitial
type.path.editorPathempty string
type.path.fileEntryIndex2

Then set paths by target:

IDsclassNamerealPath
12, 28gameJournalQuestquests/minor_quest/cqa002
13, 26gameJournalQuestObjectivequests/minor_quest/cqa002/cqa002_01/cqa002_01_obj_wait
14, 22, 23gameJournalQuestObjectivequests/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:

PropertyValue
inputSocketCount2
outputSocketCount1

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:

SourceDestination
0.Out10.In
10.False1.In
10.True11.In
11.Out12.Active
12.Out13.Active
13.Out14.Active
14.Out15.In
14.Out16.In
14.Out17.In
17.True18.In
18.Out19.In
17.False20.In
20.Out21.In
15.Out22.Failed
22.Out25.In1
16.Out23.Succeeded
23.Out24.In
24.Out25.In2
25.Out126.Succeeded
26.Out27.In
27.Out28.Succeeded
28.Out1.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:

  1. run Project > Scan for broken file references;
  2. run Project > Scan for broken files;
  3. run Project > Run File Validation on the entire project;
  4. resolve all project-owned errors;
  5. close Cyberpunk 2077;
  6. choose Build > Install and wait for packing and installation;
  7. 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:

  1. copy the completed project to a separately named working directory;
  2. open node 11;
  3. change only type.value from exact 2 to exact 1;
  4. leave setExactValue: 1 and all other files/properties unchanged;
  5. rebuild and label the edited archive as the mode-1 variant;
  6. install it only after removing the canonical archive;
  7. 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

SymptomCheck
Reopening the phase loses or misbinds connectorsRenumber through temporary IDs 100–118, save, close, reopen, and confirm the exact final ID inventory before wiring
A condition branches immediately instead of waitingIDs 15, 16, 18, and 20 must be Pause Condition nodes; IDs 10 and 17 are the only immediate Condition nodes
The stable route never releasesConfirm 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 wrongKeep 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 absentInstall both the packed archive and loose .archive.xl, then inspect ArchiveXL and RED4ext logs for the three depot paths
Canonical hashes no longer matchRestore 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.

RecordValue
Structural validation date2026-08-09
Runtime test dateNot 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

ComponentExact version
Cyberpunk 2077 for Windows (GOG)2.31a
WolvenKit8.19.0
ArchiveXL1.27.0
RED4ext1.30.0
redscript0.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:

  1. choose two manual saves that have never loaded a build registering cqa002;
  2. copy each whole save-slot directory to retained evidence storage;
  3. label one for canonical-clean and the other for source-edit-failure;
  4. record the slot-directory name;
  5. 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
  1. Close the game.

  2. Build and install the completed project with WolvenKit.

  3. Verify that only these candidate files are installed:

    archive\pc\mod\CQA_Lab02_SignalRace.archive
    archive\pc\mod\CQA_Lab02_SignalRace.archive.xl
    
  4. Confirm that all three registered depot paths in the .archive.xl match the project.

Run the canonical cases

Load only the save assigned to canonical-clean.

Stable first run

Observe and record:

  1. Signal Race activates once;
  2. both objectives activate once;
  3. the optional objective remains active before the roughly 120-second result;
  4. it succeeds after roughly 120 seconds of unpaused observation;
  5. the required objective and quest then succeed once;
  6. 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:

  1. make and retain a completed save;
  2. use it for canonical-completed-reload;
  3. reload it without changing the installed files and confirm there is no quest/objective reactivation or delayed visible journal update;
  4. retain this execution’s fresh logs;
  5. close the game, remove and reinstall the same candidate;
  6. use the same completed save for a new canonical-reinstall execution;
  7. 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:

  1. both objectives activate once;
  2. the optional objective remains active before the 30-second result;
  3. the optional objective then fails once;
  4. the required objective and quest still succeed once;
  5. 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:

  1. close the game and remove the mode-1 installed files;
  2. restore node 11 to exact value 2 or discard the edited working copy;
  3. reopen the unmodified completed checkpoint;
  4. restore either no Lab 2 installation or the unchanged canonical candidate;
  5. 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

SymptomCheck
Quest is already active or complete on the first runStop 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 observeConfirm 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 attributedKeep installed files, untouched-save labels, slot-directory identities, executions, and logs separate; identical clean-save bytes are allowed
Reload result is ambiguousRecord whether the timer resumed or restarted, but require only eventual single completion without duplicate activation
One evidence note is reused for every executionKeep 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 UIRecord 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.

RecordValue
Practical baselineCyberpunk 2077 2.31a, WolvenKit 8.19.0, ArchiveXL 1.27.0, RED4ext 1.30.0, redscript 0.5.31
Mod-owned referencecqa001 / First Signal
Reference statusLab 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

ChapterQuestion it answers
Trees and pathsHow is a .journal resource built, merged, and addressed?
Quest stateWhich entry types and graph operations produce active, inactive, succeeded, failed, tracked, and visited state?
MappinsWhich data belongs to the journal, and where does the streamed-world boundary begin?
Messages and onscreensHow do contacts, phone choices, files, emails, and readable entries differ?
Localization pathsWhy do UI text, spoken lines, and scene choices use three unrelated lookup chains?
Rewards and completionHow 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:

ContractLab 1 value
Registered journal resourcemod\cqa\cqa001\journal\cqa001.journal
Entry chainprimary folder → folder → quest → phase → objective
Expected leaf typegameJournalQuestObjective
gameJournalPath.classNamegameJournalQuestObjective
gameJournalPath.fileEntryIndex2, identifying the containing quest file entry in this path family
Requested stateThe journal node’s connected Active or Succeeded input socket
Visible text keycqa_cqa001_objective_wait
Registered text resourcemod\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.journal for journal entry families;
  • base\journal\descriptor.journaldesc for the standard descriptor contract;
  • base\quest\side_quests\sq021\phases\sq021_randys_room.questphase for a computer-document fact handoff;
  • base\open_world\street_stories\watson\kabuki\sts_wat_kab_05\phases\sts_wat_kab_05_openworld.questphase for 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 RootChunkLab 1 valuePurpose
cookingPlatformPLATFORM_PCCooking target recorded by the resource
entrygameJournalRootFolderEntry handleRoot of this contributed tree
entry.iddefault/not serialized in the checkpointThe root itself is not a named path component
entry.descriptor.DepotPathbase\journal\descriptor.journaldescSoft reference to the vanilla journal descriptor
entry.descriptor.FlagsSoftReference-loading policy used by the validated resource
entry.journalEntryOverrideDataListdefault/not serialized in the checkpointNo 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:

PropertyMeaning
realPathSlash-separated IDs from the merged journal tree
classNameCName describing the intended target entry type
fileEntryIndexZero-based component index of the containing gameJournalFileEntry
editorPathEditor-facing path text; Lab 1 leaves it empty

Lab 1’s objective target is:

PropertyValue
realPathquests/minor_quest/cqa001/cqa001_01/cqa001_01_obj_wait
classNamegameJournalQuestObjective
fileEntryIndex2
editorPathempty 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 pathContaining file-entry typefileEntryIndex
contacts/example_contact/...gameJournalContact at component 11
points_of_interest/minor_quests/...gameJournalPointOfInterestGroup at component 11
onscreens/emails/quests/minor_quest/example/files/...gameJournalFileGroup at component 55
onscreens/emails/quests/minor_quest/example/messages/...gameJournalOnscreenGroup at component 55

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:

  1. Confirm the .journal CR2W exists at the registered depot path.
  2. Confirm the ArchiveXL file contains the journal entry at the correct YAML nesting.
  3. Reopen the journal in WolvenKit and verify the root descriptor and every entry id survived serialization.
  4. Trace realPath component by component from the anonymous root.
  5. Confirm className matches the tutorial-owned target type.
  6. Recalculate fileEntryIndex from the containing file entry.
  7. 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 typeRelevant authored properties
gameJournalQuestid, title.value, type, districtID, recommendedLevelID
gameJournalQuestPhaseid, locationPrefabRef
gameJournalQuestObjectiveid, description.value, counter, optional, districtID, itemID, locationPrefabRef
gameJournalQuestDescriptionid, 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 typeLab 1 value
optional0
sendNotification1
trackQuest1
versionInitial
path.editorPathempty string
path.fileEntryIndex2

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 socketRequested journal operation
ActiveMake the target active
InactiveMake the target inactive
SucceededMark the target succeeded
FailedMark 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 IDTargetIncoming socketResult
11quests/minor_quest/cqa001ActiveActivate the quest
12quests/minor_quest/cqa001/cqa001_01/cqa001_01_obj_waitActiveActivate the objective
14same objective pathSucceededSucceed the objective after the delay
16quests/minor_quest/cqa001SucceededSucceed 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:

DimensionExample questionHow it is addressed
Lifecycle stateIs the objective active or succeeded?Journal-node state socket or state condition
Visited stateDid the player open or select the entry?questJournalEntryVisited_ConditionType
TrackingIs this quest selected for tracker/navigation presentation?trackQuest on a journal operation plus runtime UI state
NotificationWas 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:

  1. activation from a manual save created before the first install;
  2. saving and reloading while the delay and objective are active;
  3. reloading after quest success;
  4. reinstalling the identical build over the completed save;
  5. 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:

  1. Confirm the edge enters the intended state socket, especially Active versus Succeeded.
  2. Confirm type.path.realPath, className, and fileEntryIndex resolve to the supplied journal entry.
  3. Confirm the journal resource and its ArchiveXL registration loaded before diagnosing graph timing.
  4. Check whether the entry was already changed in the test save.
  5. If state changes but text is blank, compare the journal localization key with the onscreen secondaryKey.
  6. If text appears but tracking or notification differs, record that outcome separately rather than reporting the entire journal operation as failed.
  7. 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.

RecordValue
Authoring targetPinned versions
Native type and property shapesObserved in vanilla
New mod-owned mappin lifecycleExperimental 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:

OwnerResponsibility
QuestphaseDecide when the pin becomes active or inactive
Journal resourceOwn the pin entry, caption key, map style, and target reference
World resourceOwn the marker or entity identified by the NodeRef
Onscreen localizationResolve the caption key to player-facing text
SaveRetain 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.

PropertyType or domainResponsibility
idJournal entry IDFinal component of the journal path
enableGPSBooleanAllows the pin to participate in GPS routing; it does not activate the pin by itself
mappinData.activeBooleanAuthored initial presentation state; runtime journal state is still changed by quest nodes
mappinData.localizedCaption.valueLocalization keyPlayer-facing caption lookup
mappinData.mappinTypeTweakDBIDMappin definition, commonly Mappins.QuestStaticMappinDefinition in the cited shape
mappinData.variantMappin variantVisual role such as DefaultQuestVariant
mappinData.visibleThroughWallsBooleanWhether the world indicator may remain visible through geometry
reference.referenceNodeRefWorld target; this is not a journal ID or graph node ID
slotNameCNameAttachment slot such as UI_Interaction when the target exposes one
offsetVector3Local display offset from the referenced target
uiAnimationTweakDBIDOptional 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.

PropertyRequired meaning
path.classNamegameJournalQuestMapPin
path.realPathFull path to the exact pin entry
path.fileEntryIndexIndex of the containing quest file entry; 2 for a path shaped like quests/minor_quest/cqa003/...
path.editorPathNormally empty in the cited serialized resources
disablePreviousMappinsWhether this transition also clears earlier mappin/GPS state

Its normal socket contract is:

SocketDirectionMeaning
CutDestinationCut destinationInterruption obligation; do not guess a connection
ActiveInputRequest the referenced quest pin’s active state
InactiveInputRequest its inactive state
OutOutputContinue 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:

  1. the first pin appears;
  2. its GPS route targets the intended marker;
  3. inactivation removes its presentation;
  4. the next pin does not retain a stale route;
  5. 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.

ConcerngameJournalQuestMapPingameJournalPointOfInterestMappin
Typical ownerNested beneath a quest objectiveEntry beneath points_of_interest/<group>
Quest associationImplied by its containing objective pathExplicit questPath handle
World targetreference.referencestaticNodeRef and, when used, dynamicEntityRef
Presentation datagamemappinsMappinDatagamemappinsPointOfInterestMappinData
Definition fieldsmappinType, variantdynamicMappinDef, staticMappinDef, typedVariant
Questphase lifecycle in this chapterquestMappinManagerNodeDefinition Active/InactiveDo not substitute it for the quest-pin path without evidence for that exact use

The decisive POI properties are:

PropertyMeaning
mappinData.activeAuthored POI availability in the cited shape
mappinData.dynamicMappinDefDynamic POI definition TweakDBID
mappinData.staticMappinDefStatic POI definition TweakDBID
mappinData.typedVariantHandle containing a typed mappin variant
mappinData.slotName / slotOffsetAttachment slot and local offset
staticNodeRefStatic world marker
questPathTyped 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:

  1. Resolve the exact NodeRef, including its #-relative or $-absolute form.
  2. Identify the streaming sector, prefab, or entity that owns it.
  3. Confirm that resource is installed at the intended depot path.
  4. Confirm the target is loaded when the pin is activated.
  5. Check whether slotName is valid for the target or whether the pin should use the marker origin plus offset.

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

SymptomFirst boundary to inspect
Manager node runs but no pin appearspath.className, full realPath, fileEntryIndex, and ArchiveXL journal registration
Pin appears at the wrong placereference.reference, target ownership, slotName, and offset
Caption is blanklocalizedCaption.value versus the registered onscreen localization key
Pin appears but GPS does not routeenableGPS, mappin definition/variant, target resolution, and current tracking state
Old dotted route remainsExplicit inactivation order, next activation, then a focused disablePreviousMappins test
Pin works only on an old saveTreat that as suspect; repeat from a save that never loaded the custom journal/world identity
POI appears but objective pin does notVerify 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.

RecordValue
Authoring targetPinned versions
Native hierarchies and property shapesObserved in vanilla
New custom phone/device arrangementExperimental 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

TypeExact properties used by this model
gameJournalContactid, avatarID, name.value, type, isCallableDefault, useFlatMessageLayout, entries
gameJournalPhoneConversationid, 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

TypePropertyMeaning
gameJournalPhoneMessageidEntry ID used in the full journal path
senderSender role, for example NPC in the cited thread
text.valueOnscreen localization key or vanilla localization reference
delayPresentation delay setting for the message
attachmentOptional attachment; null in the simple cited messages
imageIdOptional image TweakDBID; 0 in the simple cited messages
isQuestImportantQuest-importance presentation flag
gameJournalPhoneChoiceGroupid, entriesContainer activated when the player should receive response options
gameJournalPhoneChoiceEntryid, text.valueOne player response and its label
isQuestImportantQuest-importance presentation flag
questConditionOptional 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.

FieldMessage exampleChoice-group example
type.path.classNamegameJournalPhoneMessagegameJournalPhoneChoiceGroup
type.path.realPathFull path to one messageFull path to one group
type.path.fileEntryIndex1 for a contact path1 for a contact path
type.sendNotificationWhether this activation should notifyWhether 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:

PropertyValue
path.classNamegameJournalPhoneChoiceEntry
path.realPathFull path to the individual choice, not the group
path.fileEntryIndex1 for the containing contact
stateSucceeded
inverted0 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:

  • Active means the entry was made available;
  • a choice entry’s Succeeded state means that choice won;
  • a message’s visited value 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:

GroupLeafPlayer-facing fields on the leaf
gameJournalOnscreenGroupgameJournalOnscreentitle.value, description.value, tag, iconID
gameJournalFileGroupgameJournalFiletitle.value, content.value, pictureTweak, videoResource
gameJournalEmailGroupgameJournalEmailsender.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:

  1. the journal entry exists and is registered;
  2. the controller has non-empty content;
  3. its journalPath resolves to the expected leaf type;
  4. the device element owns the intended questInfo.factName;
  5. 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:

SymptomFirst boundary to inspect
Contact or thread never appearsArchiveXL journal registration, contact ID, full path, className, and fileEntryIndex
Opening message appears but choices do notChoice-group activation through Active, then its exact journal path
Both replies appearSeparate Succeeded conditions and branch-to-reply wiring
Quest advances before the player reads the final messageActive was treated as visited, or the final visited wait is absent
Phone text is blankJournal text.value versus registered onscreen localization keys
Computer has no Files tabEmpty filesStructure, wrong persistent controller, or component CRUID mismatch; filesMenu alone is insufficient
File opens but the quest does not advancegamedeviceDataElement.questInfo.factName, then the quest’s fact condition
Journal-backed file is blankLeaf type/path mismatch, fileEntryIndex, journal registration, or localization lookup
Removed emails or files return on one saveSave-backed device persistent state; retry with a fresh identity or pre-stream save
Readable entry exists but no pickup appearsDirect 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.

RecordValue
Practical baselineCyberpunk 2077 2.31a, WolvenKit 8.19.0, ArchiveXL 1.27.0, RED4ext 1.30.0, redscript 0.5.31
Lab 1 UI resourceStructure checked; verify presentation with the Lab 1 test guide
Vanilla scene referencesObserved in vanilla

Choose the lookup by content owner

Player-facing contentSource identifierRuntime ownerArchiveXL section
Quest title, objective, description, journal mappin, phone UI, or onscreen textTextual LocalizationString.valuelocalizationPersistenceOnScreenEntrieslocalization.onscreens.<locale>
Spoken scene lineNumeric scnlocLocstringId.ruidSubtitle map, subtitle entries, VO map, and WEMlocalization.subtitles.<locale> and localization.vomaps.<locale>
Scene choice labelNumeric scnlocLocstringId.ruid reached through a screenplay optionThe scene’s embedded scnlocLocStoreEmbeddedNone; 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 pathType or valueContract
Data.RootChunkJsonResourceContainer for the localization payload
Data.RootChunk.root.DatalocalizationPersistenceOnScreenEntriesOwns entries[]
root.Data.entries[]localizationPersistenceOnScreenEntryOne merged onscreen string
entries[].primaryKey0 for mod-added textual keysKeep the established mod-entry shape; an export may quote the zero
entries[].secondaryKeyText stringMust equal the journal LocalizationString.value and be globally unique after all merges
entries[].femaleVariantText stringDefault authored text in the validated shape
entries[].maleVariantText stringOptional 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 typeDecisive properties
scnscreenplayDialogLine in screenplayStore.lines[]locstringId.ruid; its separate itemId.id is graph-local and is not the localization ID
localizationPersistenceSubtitleMapentries[]
localizationPersistenceSubtitleMapEntrysubtitleFile resource reference; the inspected quest map also uses subtitleGroup value quest
localizationPersistenceSubtitleEntriesentries[]
localizationPersistenceSubtitleEntrystringId, femaleVariant, and maleVariant
locVoiceoverMapentries[]
locVoLineEntrystringId, 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:

LocationTypeDecisive properties
Choice node optionscnChoiceNodeOptionscreenplayOptionId.id; caption is only an authoring/debug CName
Screenplay optionscnscreenplayChoiceOptionitemId.id, locstringId.ruid
Embedded storescnlocLocStoreEmbeddedvdEntries[], vpEntries[]
DescriptorscnlocLocStoreEmbeddedVariantDescriptorEntrylocaleId, locstringId.ruid, signature.val, variantId.ruid, vpeIndex
PayloadscnlocLocStoreEmbeddedVariantPayloadEntrycontent, 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:

  1. Keep vdEntries[] in contiguous locale blocks.
  2. Within each locale block, sort descriptors by the unsigned numeric value of locstringId.ruid.
  3. Keep descriptors with the same locale and locstring ID adjacent while preserving each descriptor’s vpeIndex and variantId relationship.
  4. 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.scene
  • base\quest\minor_quests\mq003\scenes\mq003_03_orbital_pod.scene
  • base\quest\minor_quests\mq007\scenes\mq007_01_gun_found.scene
  • base\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

SymptomFirst checks
Journal title or objective is blankCompare LocalizationString.value with secondaryKey; confirm globally unique key, primaryKey: 0, and localization.onscreens.<locale> registration
UI text works for one player variant onlyInspect both femaleVariant and maleVariant; repeat runtime acceptance with both intended configurations
Spoken subtitle is missingCompare line RUID with subtitle stringId; confirm ArchiveXL registers the subtitle map and that subtitleFile resolves
Subtitle displays but audio is silentCompare 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 staleFollow option ID to screenplay option, then descriptor to payload; inspect locale grouping, unsigned numeric ordering, vpeIndex, and variantId
Only one scene locale is wrongInspect 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:

  1. deserialize each mod-owned CR2W-JSON source with WolvenKit 8.19.0;
  2. serialize it back and compare the decisive types, IDs, depot paths, array order, and descriptor/payload indices;
  3. verify every textual or numeric join before packing;
  4. inspect ArchiveXL and game logs for registration, missing-resource, and lookup errors;
  5. run journal tests from a pre-install save and scene tests from a save made before that scene became active;
  6. 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.

RecordValue
Practical baselineCyberpunk 2077 2.31a, WolvenKit 8.19.0, ArchiveXL 1.27.0, RED4ext 1.30.0, redscript 0.5.31
Lab 1 completion pathStructure checked; verify completion with the Lab 1 test guide
Native reward referenceObserved 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 effectNative graph shapeWhat it does not do
Grant payoutquestRewardManagerNodeDefinition containing questGiveReward_NodeTypeDoes not set a completion fact, update the journal, or end execution
Persist one-shot completionquestFactsDBManagerNodeDefinition containing questSetVar_NodeTypeDoes not pay the player or change visible quest state
Show the quest as succeededquestJournalNodeDefinition containing questJournalQuestEntry_NodeType, entered through SucceededDoes not guarantee a reward or prevent root re-entry
End this phase routequestOutputNodeDefinition with type: TerminatingDoes 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:

StepNode and decisive property
1questPauseConditionNodeDefinition → questJournalCondition → questJournalEntryVisited_ConditionType, with the final message’s gameJournalPath and visited: 1
2questRewardManagerNodeDefinition → questGiveReward_NodeType.rewards[]
3questFactsDBManagerNodeDefinition → questSetVar_NodeType, with the completion fact, setExactValue: 1, and value: 1
4questJournalNodeDefinition → questJournalQuestEntry_NodeType, connected to the Succeeded input socket for the quest path
5questOutputNodeDefinition, 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

SymptomInspect first
Reward node runs but no payout appearsExact TweakDBID, existence and contents of the reward record, node In/Out connections, and runtime logs
Payout appears but the quest stays activeQuest journal path, className, fileEntryIndex, and connection to the quest node’s Succeeded socket
Quest succeeds but restarts on a later loadCompletion fact name/value, root one-shot condition, and whether the fact write is reachable before termination
Quest stops changing before the payoutFinal wait condition, especially visited versus merely active journal state
Payout occurs twiceParallel completion routes, reward-to-fact interruption window, checkpoint resume point, and save history
Journal succeeds but the graph remains activeConnection from the journal node’s Out socket to a Terminating output
Objective succeeds but quest does notConfirm 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:

  1. structurally inspect the reward node, its nested operation, every reward TweakDBID, and the connected socket pair;
  2. verify the completion fact has one meaning and the root guard reads that same fact;
  3. verify objective success, quest success, and termination are distinct connected operations;
  4. start from a pre-install save and record inventory, money, experience, fact, journal state, and logs before and after completion;
  5. test a save before the final presentation gate, after the reward if the game permits it, after the fact write, and after termination;
  6. reload the completed save and prove the quest neither reactivates nor pays again;
  7. 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

OwnerResponsibility in this section
worldStreamingWorldReferences the world’s compiled streaming blocks and global world resources.
ArchiveXL configurationAdds a mod-owned block to the game’s streaming registration without replacing the base streaming world.
worldStreamingBlockHolds sector descriptors, including sector depot paths, streaming bounds, categories, levels, and quest-prefab roots.
worldStreamingSectorHolds or references concrete world nodes, their placement records, and registered NodeRefs.
questQuestPhaseResourceExecutes quest logic and carries quest-prefab dependency declarations.
Journal resourceOwns journal presentation and map-pin records; it can refer to a world marker but does not place one.
SaveRetains 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:

  1. Streaming model — world, block, descriptor, sector, and registration responsibilities.
  2. Quest prefabs and NodeRefs — the single-phase dependency and lookup chain from a questphase to a sector node, plus the unresolved nested-phase boundary.
  3. Sector nodes and placement — concrete nodes, nodeData, nodeRefs, transforms, and inplace resources.
  4. Triggers and areas — outlines, notifiers, state-shaped and edge-shaped conditions.
  5. Markers and navigation — world markers, journal pins, navigation endpoints, and route expectations.
  6. Devices and persistence — entity nodes, slots, controller state, and fresh NodeRef identity.
  7. Advanced devices, interactions, and persistent state — world identity, template/component binding, command-versus-observation semantics, cancellation, and a clean-save device matrix.
  8. Location research — extracting quest-safe vanilla references without redistributing them.
  9. Lab 3: Boundary Check — the learner-facing resource and graph contract.
  10. Author Boundary Check in WolvenKit — every supplied resource and property explained.
  11. 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

SymptomInspect first
Nothing from the mod-owned world content appearsArchiveXL block registration, exact depot path, and ArchiveXL logs
One sector is absentIts block descriptor data, category, level, and streaming box
The object exists but quest logic cannot resolve itphasePrefabs, descriptor questPrefabNodeRef, full sector NodeRef, and local questphase NodeRef
The wrong object or transform is usednodeData.NodeIndex, the corresponding nodes[] entry, position, orientation, scale, and bounds
A journal pin has UI state but no expected world anchorJournal mappin data and the marker node/NodeRef are separate owners; inspect both
A device is visible but its prompt or authored content is absentEntity-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 savePersistent 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 saveTreat 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 propertyRoleEvidence boundary
dataAsync resource reference to the sector depot pathObserved in vanilla
streamingBoxWorld-space box used by the streaming systemShape Observed in vanilla; exact load/unload behavior must be runtime-tested
questPrefabNodeRefFull quest-prefab root associated with a Quest descriptorObserved in vanilla and part of the structurally validated prior fixture
numNodeRanges and variantsCooked streaming metadataObserved in vanilla; do not invent semantics from field names
blockIndexDescriptor’s block associationObserved in vanilla
level and categoryStreaming classification valuesValues 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:

SectorContentWhy it is separated
QuestReach and leave trigger nodesThe descriptor can bind the quest-prefab root used by quest logic.
AlwaysLoadedCheckpoint marker nodeIts 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

  1. Open the block and every referenced sector in the pinned WolvenKit version.
  2. Confirm exact depot paths, descriptor categories, levels, bounds, and quest-prefab roots.
  3. Save, convert or pack as required, then round-trip the CR2W and re-inspect the same properties.
  4. Confirm the intended files are present in the packed archive.
  5. Confirm ArchiveXL registered the mod-owned block in its log.
  6. 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

SymptomLikely boundary
No mod-owned sector appearsArchiveXL config not installed, block path misspelled, or registration failed
One sector fails while another worksIts descriptor data path, category, level, or serialized sector is wrong
The sector appears only at surprising distancesDescriptor box, node bounds, and streaming-related node fields need isolated testing
A trigger exists but a quest reference does not resolveStreaming succeeded; inspect the separate quest-prefab and NodeRef chain
WolvenKit opens the file but the game ignores itEditor acceptance did not prove packing, registration, external references, or runtime compatibility
A resource edit appears ineffective on an old saveQuest 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:

LinkOwner and propertyExample form
Direct dependencyquestQuestPhaseResource.phasePrefabs[] → questQuestPrefabEntry.prefabNodeRef#cqa003_pr_boundary
World bindingworldStreamingSectorDescriptor.questPrefabNodeRef$/mod/cqa/cqa003/#cqa003_pr_boundary
Placed child identitysector nodeData[].QuestPrefabRefHash$/mod/cqa/cqa003/#cqa003_pr_boundary/#cqa003_tr_reach
Registered child identitysector nodeRefs[]The same full child path
Concrete objectsector nodes[], associated with nodeData[].NodeIndex in the compact authored shape used by this labA 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.

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:

  1. Find the local NodeRef in the quest node or condition payload.
  2. Find its root in that questphase’s phasePrefabs.
  3. Find the same root in full form on the intended block descriptor.
  4. Find the full child path in the sector’s nodeRefs and node data.
  5. In the authored mod sector, confirm NodeIndex associates that placement with the intended concrete node; do not infer the relation from row order.
  6. 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.phaseInstancePrefabs on 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

SymptomInspect
Quest logic cannot find a visible objectLocal child spelling, phase root declaration, and full world registration
phasePrefabs is populated but nothing exists in worldBlock registration, descriptor path, sector, concrete node, and node data
Sector loads but one child is unresolvedFull child in nodeRefs, QuestPrefabRefHash, and NodeIndex
Parent resolves a child but an external child phase does notBoth dependency locations, the parent/child activation shape, and the child resource; do not assume either inheritance or mandatory duplication
Two similar roots behave differentlyCompare every segment, delimiter, case, and the descriptor-to-sector pairing; do not rely on visual similarity
Behavior differs only on an old saveInspect 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 structureResponsibility
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:

  • Position as a Vector4;
  • Orientation as a quaternion;
  • Scale, Pivot, and Bounds;
  • NodeIndex and QuestPrefabRefHash;
  • CookedPrefabData, MaxStreamingDistance, and UkHash1;
  • fields exposed by WolvenKit as UkFloat1 and Uk10 through Uk14.

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:

  1. Inspect the concrete nodes[] object and explain every behavior-specific property the example supplies.
  2. In the compact mod-owned sector, find the placement record whose NodeIndex is authored to associate with that object.
  3. Verify position, orientation, scale, pivot, and bounds against the intended site.
  4. Verify QuestPrefabRefHash is the intended full child NodeRef.
  5. Confirm nodeRefs[] registers that identity when another resource must address it.
  6. Review local and external inplace-resource ownership independently.
  7. Save and round-trip the resource, then re-open it and repeat the checks.
  8. 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

SymptomInspect
A different concrete node receives the transformThe authored nodeData.NodeIndex association; never rely on placement row order alone
Quest lookup fails for one placed nodeFull QuestPrefabRefHash, nodeRefs[], descriptor root, and questphase local path
Area appears offset or rotatedPosition, quaternion orientation, pivot, scale, outline-local points, and Z coverage
Behavior changes near the site edgeDescriptor box, node bounds, streaming-distance fields, and test approach direction
An entity node exists but a referenced template is missingCheck the separate external/local inplace-resource layer and exact depot path
Editor save succeeds but runtime behavior is absentRound-trip structure, archive contents, ArchiveXL registration, external resources, and runtime logs are separate gates
Old saves disagree with clean testsSaved 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:

  1. Review the intended local points, point order, and height.
  2. Regenerate or edit the authoritative buffer through a supported, reproducible authoring path.
  3. Round-trip the CR2W and verify the buffer still decodes to the intended count, points, and height.
  4. Review the node transform and vertical placement separately.
  5. 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:

PropertyResponsibility
typeSelects edge-shaped or state-shaped trigger semantics
triggerAreaRefLocal NodeRef of the area being queried
activatorRefExplicit activator identity where the authored shape uses one
isPlayerActivatorSelects 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

typeQuestion askedImportant test boundary
EnteredDid the activator cross from outside to inside?An already-inside reload may not produce a new entry edge.
ExitedDid the activator cross from inside to outside?Starting outside is not the same event as leaving.
IsInsideIs the activator currently inside?Suitable for a state check, but reload evaluation still needs runtime evidence.
IsOutsideIs 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:

AreaConditionEncoded geometry
#cqa003_tr_reachIsInside25-unit radius, 16 points, height 12
#cqa003_tr_leaveIsOutside110-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.

CaseWhat it distinguishes
Start outside both areasNegative control for premature IsInside success
Walk into the reach areaNormal state or edge activation path
Save before reaching, then reloadQuest and journal state restoration before a crossing
After reach succeeds, save inside the outer leave area and reloadThe active IsOutside wait stays false until departure
Leave the outer areaOrdered leave behavior and vertical/outline coverage
Reload after completionOne-shot fact, journal cleanup, and stale trigger state
Reinstall and use an old saveDemonstrates 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

SymptomInspect first
Condition never changesTrigger notifier, local/full NodeRef chain, sector registration, and activator selection
Trigger fires at a default squareAuthoritative AreaShapeOutline.buffer, not only visible points
Trigger is offsetNode placement, outline-local coordinates, pivot, orientation, and scale
Enter works but an inside reload stallsEntered versus IsInside, then retained runtime evidence for that exact graph
Leave succeeds before reachGraph activation order and premature IsOutside evaluation
Behavior differs by elevationBase Z, trailing buffer height, placement bounds, and real site geometry
A clean run works but an old save does notFacts, 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

RecordOwnerWhat it does not prove
worldStaticMarkerNodeStreaming sectorIt does not create a journal entry or activate UI.
gameJournalQuestMapPinJournal resourceIt does not place or stream its target.
questMappinManagerNodeDefinitionQuestphaseIts Out signal does not prove the HUD redrew or GPS found a route.
scnWorldMarkerScene/quest scene-location payloadIt 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:

PropertyResponsibility
reference.referenceTarget NodeRef
enableGPSAllows routing participation; it does not guarantee a route
mappinData.mappinTypeMappin definition TweakDBID
mappinData.variantVisual role, such as DefaultQuestVariant in the cited shape
mappinData.localizedCaption.valueOnscreen localization key
reference.slotNameOptional slot exposed by an entity target
offsetLocal 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

SymptomInspect first
No pin, but the marker existsJournal path, mappin manager socket, journal registration, and saved journal state
Pin appears at the origin or wrong placereference.reference, full/local NodeRef chain, target load scope, slot, and offset
Icon is too low or highMarker transform versus entity slot transform and journal offset
Pin appears but no GPS route existsTracking state, enableGPS, target resolution, route endpoint, and nearby traversable space
A stale dotted route remainsExplicit prior inactivation and a focused disablePreviousMappins replacement test
A scene starts but the quest pin failsscnWorldMarker and journal mappin are separate records; inspect both owners
Old saves disagree with clean runsJournal 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.

ConcernworldEntityNodeworldDeviceNode
Entity-template placementYes, in observed contextsYes, in observed contexts
Node-local instanceDataPresent in observed entity examplesPresent in mod-owned and vanilla device research shapes
Device-specific world fieldsNot established by the generic type nameRetained shapes expose fields such as device class, alpha-hack streaming override, and entity streaming properties
Controller behaviorComes from the template and/or bound package, not the node name aloneComes 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:

  1. Inspect the exact entity template and component hierarchy.
  2. Record the component transform and slot-local transform.
  3. Apply the owning entity’s world transform.
  4. 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:

FormObserved contextExample
Local childQuestphase device-manager target beneath available prefab dependencies#q108_dvc_door_to_soulkiller
Local device-prefab rootA phase-node dependency that makes a device namespace available#loc_sq021_trailer_park_devices
Local device targetQuest logic in that inspected location context#sq021_randy_pc
Absolute world identityCross-resource world/device lookup and sector registrationThe 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.

RequirementEvidence to collect
Visible entitySector registration, node placement, entity template, appearance
InteractionTemplate components, slots/workspots, controller binding
Quest device-manager actionExact NodeRef context, controller class, lookup/registry behavior
Persisted controller stateFresh-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:

  1. Record its full NodeRef, source sector, NodeIndex, transform, and node type.
  2. Inspect its exact entity-template path and appearance mapping.
  3. Inspect template components, CRUIDs, workspots, and relevant slots.
  4. Determine whether node-local instanceData exists and inventory its RedPackage chunks without copying the whole payload.
  5. Follow sector inplace references separately.
  6. Search .devices and .psrep for the exact identity, but treat absence and presence as evidence for that device only.
  7. Inspect the quest node or condition that addresses it and record whether the NodeRef is local or absolute in that context.
  8. 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

SymptomInspect first
Device is visible but action data is missingTemplate/component binding and node-local RedPackage CRUIDs
Journal icon appears at the wrong heightExact template slot and transform, not a presumed universal slot
Quest device-manager node cannot find a usable deviceLocal/absolute NodeRef context, prefab dependency, controller class, and .devices evidence
Adding .devices changes nothingVerify that the failed behavior actually consumes that registry and that the sparse patch merged
State returns after removing an overrideSave-backed device identity; use a clean save or fresh NodeRef
Inplace extraction lacks controller dataCheck node-local instanceData; do not conflate RedPackage with .streamingsector_inplace
A copied controller works on one template onlyTemplate-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
OwnerWhat it decidesWhat it cannot prove alone
Streaming sectorConcrete node, transform, registered NodeRef, and node-local instance dataThat the interaction is reachable or the quest can resolve it
Entity templateComponents, controller identities, appearances, slots, and workspots available to the entityThat copied instance data binds or that a particular action string is accepted
Node-local RedPackageInstance-specific controller/component state in observed devicesThat global registry or save behavior is unnecessary elsewhere
.devices / .psrep familyContext-dependent lookup and persistence dataA universal prerequisite for every Files UI or quest operation
QuestphaseWhen to issue a command, what signal to observe, and how to advanceCreation of the entity, prompt, animation, or persistent controller implementation
Journal/factsPlayer-facing and durable quest outcomesAutomatic device reset or removal
SavePreviously materialized graph and persistent stateThat 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:

PurposeNative shapeDecisive fields
Issue a quest-owned commandquestInteractiveObjectManagerNodeDefinition -> questDeviceManager_NodeType -> questDeviceManager_NodeTypeParamsobjectRef, slotName, entityRef, deviceControllerClass, deviceAction, actionProperties
Observe controller statequestPauseConditionNodeDefinition -> questObjectCondition -> questDevice_ConditionTypeobjectRef, 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 familyCandidate authoritative signalBoundary
Journal-backed computer fileThe exact gameJournalFile.questInfo.factName written when that file opensProves that configured file signal only, not that the player read every line or visited another tab
Personal-link actionEvidence-matched controller function such as the tested ScriptableDeviceComponentPS.IsPersonalLinkConnected, followed by the authored operation and disconnectThe tested route proves its exact package; success, cancel, and disconnect are separate states
Quest-forced door stateEvidence-matched controller condition or a downstream traversal fact/triggerIssuing the manager command is not proof that collision, navigation, or companions traversed safely
Drop pointEngine-owned deposit fact after the exact reservation event and item handoffA reservation request is not proof of item removal or kiosk accessibility
Generic promptController state, explicit quest fact, journal signal, or later world condition bound to that promptPrompt 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:

EventPossible ownerRequired design decision
Player closes a device UIDevice/UI controllerIs the operation retriable, and which state proves the UI actually closed?
Player breaks a personal-link interactionDevice/workspot/controllerIs a disconnect action required, and are items or facts unchanged?
Quest branch becomes irrelevantQuest graphIs the waiting signal made harmless, or does a separately tested cut target it?
Scene using the same entity interruptsScene plus quest wrapperWhich named interruption/return outcome retains or releases the device?
Device streams outWorld streaming plus persistent controllerDoes the interaction resume, fail, or require the player to approach again?
Quest completes or is removedRoot lifecycle and saveShould 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:

  1. Create or copy a mod-owned WolvenKit project; do not edit an installed lab or a base-game resource in place.
  2. Extract the exact vanilla comparison from your own game and record its sector, node type, entity-template path, full NodeRef, transform, and instanceData presence.
  3. Inspect the entity template’s controller, scanner, interaction, slot, and workspot components. Record their names and CRUIDs before editing the placement package.
  4. 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.
  5. 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.
  6. Add .devices or other persistence resources only when the exact native comparison or a controlled missing-resource test requires them.
  7. 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.
  8. 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.
  9. Route success, retryable cancellation, and terminal failure before adding item removal, journal completion, or a durable completion fact.
  10. 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.
  11. Save, close, reopen, and serialize the resources. Inspect concrete types, non-null handles, component IDs, NodeRefs, controller CName values, function/action arrays, sockets, and all world registration paths.
  12. 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.

CaseRequired observation
Fresh approachEntity appears once; prompt, workspot, slots, and navigation are usable at the final transform
Normal successPlayer-driven input is required when promised; authoritative signal occurs once; inventory/journal/facts advance in the authored order
Cancel before commitmentNo success fact, item consumption, or objective success; UI/workspot/controller releases; retry remains possible
Interrupt during operationThe authored failure, return, or retry policy occurs without duplicate effects or a stuck player/actor
Reload before interactionPrompt and objective restore without auto-completing
Reload during a supported interaction stateController, UI/workspot, and quest state recover according to the recorded policy; if saving is unavailable, record that limitation
Reload after successFinal state and progression remain coherent; the action does not replay
Stream away and returnDevice resolves once and preserves or re-evaluates exactly the state the design claims
Repeated useOne-shot content cannot duplicate a reward or item mutation; repeatable content follows its explicit reset policy
Old-save negative controlPreviously streamed identity is kept out of positive evidence or is documented as intentionally migrated
Removal isolationRemoving the archive from a copied save does not support claims about reset; record lingering save-backed state separately
Second untouched saveThe exact candidate and route reproduce without inherited controller, fact, journal, or graph history

Failure routing

SymptomInspect first
Device appears but authored content is absentEntity-template component names/CRUIDs and node-local RedPackage binding
Prompt is absentInteraction component/TweakDB setup, workspot and slot ownership, controller eligibility, placement clearance
Quest command does nothingobjectRef context, controller class, action name/properties, prefab dependency, and evidence for registry lookup
Player can interact but objective never advancesWhether the condition/fact belongs to that exact interaction result and whether the signal was already true
Objective advances without inputA quest-issued action performed the promised player operation or an old save retained the completion state
Cancel consumes the item or writes successCommitment writes occur before the authoritative result or cancel and success converge too early
Door moves but traversal failsCollision, navigation, obstruction, companions, and the concrete final controller state
Old content returns after repackingReused NodeRef and save-backed controller state; use an untouched save or fresh experimental identity
Added .devices/.psrep changes nothingThe 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 pathQuestion it can answer
base\worlds\03_night_city\_compiled\default\03_night_city.streamingworldWhich global resources and compiled block list the native world root references
base\worlds\03_night_city\_compiled\default\blocks\all.streamingblockWhich descriptor points to a sector, with which category, level, box, and quest-prefab root
base\worlds\03_night_city\_compiled\default\quest_606b61008df2ba6f.streamingsectorHow one focused Quest sector relates nodes, placements, NodeRefs, triggers, and a quest-prefab namespace
base\worlds\03_night_city\_compiled\default\always_loaded_0.streamingsectorHow 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.streamingsectorWhere 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.streamingsectorWhere that terminal’s linked fast-travel destination marker is registered and placed
base\localization\en-us\onscreens\onscreens.jsonWhich player-facing English label is assigned to the terminal record’s localization key
base\quest\main_quests\part1\q108\phases\q108_06b_tower_mainframe.questphaseHow one vanilla phase declares prefab dependencies and uses local trigger, device, scene-marker, AI-spot, and spline references
base\journal\cooked_journal.journalHow 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.streamingsectorHow 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_inplaceWhat that placement’s separate inplace resource embeds—and what it does not own
base\gameplay\devices\drop_points\drop_point.entWhich components and local UI/navigation slots one drop-point template exposes
base\worlds\03_night_city\_compiled\default\03_night_city.devicesWhether 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:

FieldFocused value
TweakDB recordFastTravelPoints.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 templatebase\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:

FieldFocused 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:

FieldExample of the required precision
Observation date and game buildExact build/storefront used for the in-game visit
WolvenKit versionExact version used to extract and serialize the focused view
Depot provenanceExact paths and archive source for every retained claim
World identityFull NodeRef plus local form only where its resolution context is known
PlacementPosition, quaternion or yaw, scale, pivot, bounds, and coordinate source
Resource chainRoot, block descriptor, sector, node-data row, node, template, and optional inplace/registry owners
Focused excerptOnly decisive fields, with array indices or stable identifiers
Evidence labelOne of the four exact confidence labels, attached to the bounded claim
Runtime caseStarting 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:

  1. Choose a unique namespace, prefab root, and child NodeRefs.
  2. Author a mod-owned sector with only the explained nodes.
  3. Author a mod-owned block and register it through ArchiveXL.
  4. Reference vanilla entity templates by depot path only when redistribution is unnecessary and the dependency is intentional.
  5. Preserve opaque cooked values from a known-good mod-owned seed or editor output; do not invent meanings for Uk* properties.
  6. Round-trip, validate, pack, and inventory the archive.
  7. 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

CaseEvidence sought
Approach from each practical directionStreaming, collision, access, marker height, and trigger boundary
Approach from a different elevationVertical trigger coverage and endpoint placement
Start outsideNegative control for state conditions and premature UI
Save/reload before entryRegistration and quest-state restoration
After reach, save/reload inside the outer leave areaActive leave-state behavior and loaded target resolution
Stream away and return before reachVisible continuity and lookup errors while the objective is active; no precise unload claim without instrumentation
Fast travel nearbySeparate load/route case; never substitute it for an ordinary crossing
Complete and reloadOne-shot guard, journal cleanup, and persistent world/device state
Remove/reinstall on an old saveDemonstrates 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

SymptomLikely research mistake
Correct coordinates, wrong sectorMultiple vertical/grid layers or a nearby composite prefab were conflated
NodeIndex resolves the wrong nodePlacement row order was treated as array identity
Copied reference works only in vanillaIts prefab root, variant, global registry, or save identity was an unstated dependency
Marker is visible but route ends badlyVisual anchor and traversable navigation endpoint were treated as the same point
Device appears but custom behavior is absentTemplate, node-local package, registry, and persistence owners were collapsed into one assumption
Trigger looks correct in properties but fires as a squareThe visible points were trusted instead of AreaShapeOutline.buffer
Result changes after reinstallSave-backed facts, journal, checkpoints, or device state contaminated the comparison
Research folder is too large to reviewWhole 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.

RecordValue
Structural validation date2026-08-09
Runtime test dateNot 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:

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.

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

ResourceDepot pathOwns
Root questphasemod\cqa\cqa003\phases\cqa003.questphasePhase graph and root prefab declaration
Journalmod\cqa\cqa003\journal\cqa003.journalQuest, phase, two objectives, and the reach-objective map pin
Onscreen localizationmod\cqa\cqa003\localization\en-us\onscreens\cqa003.jsonTitle, two objectives, and pin caption
Streaming blockmod\cqa\cqa003\world\cqa003_boundary.streamingblockQuest and AlwaysLoaded sector descriptors
Quest sectormod\cqa\cqa003\world\cqa003_boundary.streamingsectorReach and leave trigger node definitions and placements
AlwaysLoaded sectormod\cqa\cqa003\world\cqa003_always_loaded.streamingsectorStatic marker definition and placement
ArchiveXL registrationCQA_Lab03_BoundaryCheck.archive.xlRoot 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

OwnerCategory / levelNodePlacementGeometry or orientation
Quest sectorQuest / 255worldTriggerAreaNode reach(-1000.02, 1497.2208, 2.3)16-point, 25 m radius, 12 m high
Quest sectorQuest / 255worldTriggerAreaNode leave(-1000.02, 1497.2208, 0.3)20-point, 110 m radius, 16 m high
AlwaysLoaded sectorAlwaysLoaded / 1worldStaticMarkerNode(-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 entryLocalization keyEnglish text
Quest titlecqa_cqa003_titleBoundary Check
Reach objectivecqa_cqa003_objective_reachReach the marked checkpoint.
Leave objectivecqa_cqa003_objective_leaveLeave the checkpoint area.
Map-pin captioncqa_cqa003_mappin_checkpointBoundary 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.

IDRED node typeDecisive payloadPurpose
0questInputNodeDefinitioninterface In1Enter registered root
1questOutputNodeDefinitionterminating Out1End success and bypass routes
10questConditionNodeDefinitioncqa003_completed Equal 0Bypass a completed save
11questJournalNodeDefinitionquest pathActivate Boundary Check
12questJournalNodeDefinitionphase pathActivate phase
13questJournalNodeDefinitionreach objectiveActivate reach objective
14questMappinManagerNodeDefinitionmap-pin path, disable previous 0Activate/track checkpoint pin
15questPauseConditionNodeDefinitionplayer IsInside #cqa003_tr_reachWait for current inside state
16questMappinManagerNodeDefinitionsame map-pin pathInactivate checkpoint pin
17questJournalNodeDefinitionreach objectiveSucceed reach objective
18questJournalNodeDefinitionleave objectiveActivate leave objective
19questPauseConditionNodeDefinitionplayer IsOutside #cqa003_tr_leaveWait for current outside state
20questJournalNodeDefinitionleave objectiveSucceed leave objective
21questJournalNodeDefinitionphase pathSucceed phase
22questFactsDBManagerNodeDefinitionset cqa003_completed = 1 exactlyPersist one-shot completion
23questJournalNodeDefinitionquest pathSucceed 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

SourceDestinationMeaning
0.Out10.InEnter guard
10.False1.InCompleted-save bypass
10.True11.ActiveFirst-run activation
11.Out12.ActiveActivate phase
12.Out13.ActiveActivate reach objective
13.Out14.ActiveActivate pin
14.Out15.InArm reach condition
15.Out16.InactiveRetire pin after reach
16.Out17.SucceededSucceed reach objective
17.Out18.ActiveActivate leave objective
18.Out19.InArm leave condition
19.Out20.SucceededSucceed leave objective
20.Out21.SucceededSucceed phase
21.Out22.InWrite completion fact
22.Out23.SucceededSucceed quest
23.Out1.InTerminate 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

SymptomCheck firstBoundary
Quest never appearsInstalled archive plus loose .archive.xl, root phase/journal/localization paths, ArchiveXL and RED4ext logsStructural validation does not establish runtime activation
Objective appears but pin does notJournal map-pin path, marker NodeRef, AlwaysLoaded descriptor, and Mappin Manager state socketDo not infer GPS or sector behavior from the objective alone
Pin appears at the wrong heightMarker placement Z, yaw, terrain accessibility, and clean starting saveJudge inherited coordinates from the exact candidate’s retained runtime evidence
Entering does nothingQuest-sector descriptor root, full child ref, local condition ref, player activator flag, trigger notifier, and decoded reach bufferJSON points alone are not geometry evidence
Leave completes immediatelyConfirm the starting save is inside the outer volume when leave activates and the condition is IsOutsideA dirty save or bad vertical coverage changes the initial state
Area works until streaming awayFinite Quest bounds, sector logs, return route, and NodeRef resolutionOne successful nearby crossing does not prove unload/reload behavior
Quest reactivates after reloadcompleted write order, guard comparison, exact installed build, and untouched/completed save identityConsole 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.

RecordValue
Structural validation date2026-08-09
Runtime test dateNot 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

  1. Download and extract the start ZIP.
  2. Open CQA_Lab03_BoundaryCheck_Start.cpmodproj with File > Open Project.
  3. Make an untouched backup of the extracted directory.
  4. Confirm that source\archive and source\raw each contain the same six depot resources listed in the lab overview.
  5. Confirm CQA_Lab03_BoundaryCheck_Start.archive.xl is under source\resources.
  6. 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:

PropertyValue
enableGPS1
reference.reference#cqa003_mp_checkpoint
offset(0, 0, 0.5)
mappinData.active0
mappinData.mappinTypeMappins.QuestStaticMappinDefinition
mappinData.variantDefaultQuestVariant
mappinData.visibleThroughWalls1
localized/debug captioncqa_cqa003_mappin_checkpoint

Open the onscreen localization resource and confirm four entries with primary key 0, empty male variants, and these exact joins:

secondaryKeyfemaleVariant
cqa_cqa003_titleBoundary Check
cqa_cqa003_objective_reachReach the marked checkpoint.
cqa_cqa003_objective_leaveLeave the checkpoint area.
cqa_cqa003_mappin_checkpointBoundary 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:

PropertyQuest descriptorAlwaysLoaded descriptor
categoryQuestAlwaysLoaded
level01
data...\cqa003_boundary.streamingsector...\cqa003_always_loaded.streamingsector
questPrefabNodeRef$/mod/cqa/cqa003/#cqa003_pr_boundaryzero/unset
numNodeRanges11
variantsemptyempty

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.

NodeIndexLocal nodeFull QuestPrefabRefHashPosition
0reach worldTriggerAreaNode$/mod/cqa/cqa003/#cqa003_pr_boundary/#cqa003_tr_reach(-1000.02, 1497.2208, 2.3)
1leave 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:

ChildMaxStreamingDistanceUkFloat1
reach320280
leave360320

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 refOutline pointsRadiusHeight
#cqa003_tr_reach162512
#cqa003_tr_leave2011016

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.

PropertyValue
Full placement ref$/mod/cqa/cqa003/#cqa003_pr_boundary/#cqa003_mp_checkpoint
Position(-1000.02, 1497.2208, 8.3)
Yaw88.6°
MaxStreamingDistance360
UkFloat1320 (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 IDAdd Node labelRED type
10ConditionquestConditionNodeDefinition
11JournalquestJournalNodeDefinition
12JournalquestJournalNodeDefinition
13JournalquestJournalNodeDefinition
14MappinManagerquestMappinManagerNodeDefinition
15PauseConditionquestPauseConditionNodeDefinition
16MappinManagerquestMappinManagerNodeDefinition
17JournalquestJournalNodeDefinition
18JournalquestJournalNodeDefinition
19PauseConditionquestPauseConditionNodeDefinition
20JournalquestJournalNodeDefinition
21JournalquestJournalNodeDefinition
22FactsDBManagerquestFactsDBManagerNodeDefinition
23JournalquestJournalNodeDefinition

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:

PropertyValue
factNamecqa003_completed
comparisonTypeEqual
value0

At ID 22, choose questSetVar_NodeType and set:

PropertyValue
factNamecqa003_completed
setExactValue1
value1

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:

PropertyValue
type.optional0
type.sendNotification1
type.trackQuest1
type.versionInitial
type.path.editorPathempty
type.path.fileEntryIndex2
IDsclassNamerealPath
11, 23gameJournalQuestquests/minor_quest/cqa003
12, 21gameJournalQuestPhasequests/minor_quest/cqa003/cqa003_01
13, 17gameJournalQuestObjectivequests/minor_quest/cqa003/cqa003_01/cqa003_01_obj_reach
18, 20gameJournalQuestObjectivequests/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:

PropertyValue
path.classNamegameJournalQuestMapPin
path.realPathquests/minor_quest/cqa003/cqa003_01/cqa003_01_obj_reach/cqa003_01_qmp_checkpoint
path.fileEntryIndex2
path.editorPathempty

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:

IDcondition.typetriggerAreaRef
15IsInside#cqa003_tr_reach
19IsOutside#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:

SourceDestination
0.Out10.In
10.False1.In
10.True11.Active
11.Out12.Active
12.Out13.Active
13.Out14.Active
14.Out15.In
15.Out16.Inactive
16.Out17.Succeeded
17.Out18.Active
18.Out19.In
19.Out20.Succeeded
20.Out21.Succeeded
21.Out22.In
22.Out23.Succeeded
23.Out1.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:

  1. run Project > Scan for broken file references;
  2. run Project > Scan for broken files;
  3. run Project > Run File Validation on the entire project;
  4. resolve all project-owned errors;
  5. close Cyberpunk 2077;
  6. choose Build > Install and wait for packing and installation;
  7. 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

SymptomCheck
Graph sockets move or disappear after reopenRenumber through 100–113, save, close, reopen, then wire only after final IDs are unique
Trigger wait never releasesConfirm Pause Condition, questTriggerCondition, player activator, exact local ref, matching full sector child ref, notifier, and decoded outline
Leave completes as soon as it activatesConfirm outer volume and Z coverage, IsOutside, and that the test save is actually inside the outer volume at reach success
Pin cannot resolveKeep marker full ref, journal local ref, gameJournalQuestMapPin path, and active/inactive Mappin Manager operations aligned
Block exists but sectors do not appearKeep both descriptor data paths, Quest root ref, category/level pairs, finite Quest bounds, and ArchiveXL block registration
Editing points changes nothingThe AreaShapeOutline.buffer is authoritative; update and revalidate the complete outline serialization
Build succeeds but nothing startsConfirm installed archive plus loose registration file and inspect logs for all four registered top-level resources
Canonical hashes no longer matchUse 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.

RecordValue
Structural validation date2026-08-09
Runtime test dateNot 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

ComponentExact version
Cyberpunk 2077 for Windows (GOG)2.31a
WolvenKit8.19.0
ArchiveXL1.27.0
RED4ext1.30.0
redscript0.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:

  1. with no Lab 3 candidate installed, visit and unlock Allen Street;
  2. confirm the terminal opens normally and that at least one distant destination is already unlocked;
  3. from the terminal, walk roughly northeast for about 89 m to the outdoor recycling-station cabinet row;
  4. recognize the cabinet row independently of any quest marker and confirm an ordinary public approach, collision, and practical elevation;
  5. 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:

  1. choose the Allen Street manual save made above, which has never loaded a build registering cqa003;
  2. confirm the player is outside the 25-metre reach volume and can reach the cabinet row by the verified ordinary route;
  3. copy the entire save-slot directory into your test folder;
  4. record its label and slot-directory name;
  5. confirm that this save predates the first Lab 3 install;
  6. 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;
  7. reserve separate derived copies for between-boundaries and completed reloads;
  8. 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
  1. Close the game.

  2. Build and install through WolvenKit.

  3. Confirm only this candidate’s pair is installed:

    archive\pc\mod\CQA_Lab03_BoundaryCheck.archive
    archive\pc\mod\CQA_Lab03_BoundaryCheck.archive.xl
    
  4. Confirm the archive contains all six depot resources, including the two sectors reached through the registered block.

  5. 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:

  1. Boundary Check, its phase, and the reach objective activate once;
  2. the checkpoint pin appears at the intended location;
  3. the minimap/world-map presentation and any GPS route are usable;
  4. waiting outside the reach boundary does not advance the objective;
  5. 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:

  1. reload without changing the installation;
  2. confirm the reach objective and pin return once;
  3. enter the reach volume;
  4. confirm one pin retirement and one reach success;
  5. 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:

  1. reload in place;
  2. confirm leave stays active instead of completing on load;
  3. remain inside briefly as a negative control;
  4. follow the Allen Street route past the terminal and cross the outer boundary on foot;
  5. 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.

  1. remain outside the reach volume and go to the recognizable Allen Street terminal by ordinary movement; do not open or use it;
  2. 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);
  3. record the farthest map location or player position and whether the active objective and pin remain coherent;
  4. reverse the same street route, pass the Allen Street terminal again, then follow the learned roughly 89 m northeast route to the cabinet row;
  5. 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.

  1. after loading, confirm the leave objective is active at the cabinet row;
  2. walk the learned ordinary route southwest to the Allen Street terminal;
  3. 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;
  4. open Allen Street and choose any already-unlocked destination that you independently confirmed is outside the 110 m leave outline;
  5. after arrival, record whether IsOutside completes, 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:

  1. preserve the completed save;
  2. reload it without changing installed files as completed-reload;
  3. confirm no quest/objective reactivation and no checkpoint pin;
  4. wait for delayed visible updates;
  5. 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

SymptomCheck
Quest is already active on the first runReplace the whole slot with one captured before any cqa003 install; fact resets do not clean journal/world state
Pin appears but route cannot reach itRecord 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 ordinarilyStop before acceptance, retain the observed route/access result, and establish a separately reviewed route; do not substitute a diagnostic teleport
Trigger result is ambiguousRecord 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 StreetRetain 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 reloadVerify 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 inferredReport only visible continuity and logs unless instrumentation independently establishes unload/reload
Reinstall is not identicalCompare both installed filenames; a new build is a new candidate even when source looks unchanged
One screenshot is used for every caseRetain separate run identity, saves, timestamps, observations, and four-log bundles
UI is treated as proof of sector ownershipUse 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

OwnerResponsibility
worldCommunityRegistryNodeMakes community template data and initial entry state available independently of the local streamed area.
worldCompiledCommunityAreaNode_StreamablePlaces the community source in the streamed world and mirrors which entry/phase/time period uses which AI-spot identities.
worldAISpotNodePlaces an AIActionSpot and names the workspot resource an actor can use there.
communitySpawnEntryAssociates one community entry name with a character TweakDBID and one or more spawn phases.
communitySpawnPhaseSelects the phase name, appearance set, and time-period behavior for that entry.
Character.* TweakDB recordSelects the entity template and supplies inherited reaction, faction, action-map, archetype, equipment, voice, and other gameplay defaults.
entEntityTemplateOwns the puppet scaffold, root components, default exposed appearance, and exposed-to-internal appearance mappings.
appearanceAppearanceResourceOwns internal appearance definitions, component graphs, frame type, and render/runtime dependencies.
questSpawnManagerNodeDefinitionSends Activate or Deactivate to a community reference; it does not prove the actor already exists or is gone.
questCharacterSpawned_ConditionTypeWaits for the configured spawned-character comparison to become true.
questPuppetAIManagerNodeDefinitionRequests an AI/story tier for selected spawned actors.
questMiscAICommandNodeCarries a typed AI command such as follower/patrol role assignment or role clear.
questUseWorkspotNodeDefinitionIssues a direct workspot command to an actor; its output does not replace state observation.
scnActorDef with acquisitionPlan: communityAcquires an already available community entry for scene performance.
SaveRetains 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:

  1. the community/source identity shared by the compiled area’s sourceObjectId and its registry item’s communityId;
  2. the separate world-node identity of the registry node itself;
  3. 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:

  1. 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.
  2. Entries, phases, and AI spots — how an entry selects a character record, appearance, time period, and placed workspot.
  3. Activation, readiness, and acquisition — why Activate, CharacterSpawned, broad setup, and scene acquisition are four different lifecycle steps.
  4. Cleanup and character safety — delayed deactivation, save-aware tests, unique-character hazards, and the bounded generic-character recommendation.
  5. Character records, entities, and appearances — the native record-to-entity-to-appearance chain, typed identities, mod-owned authoring boundary, and render/lifecycle acceptance matrix.
  6. 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 pathQuestion it answers
base\quest\minor_quests\mq003\mq003_orbitals.questphaseHow a vanilla parent activates a whole community, waits for spawned readiness, and invokes the activity phase.
base\quest\minor_quests\mq003\phases\mq003_homeless.questphaseHow named community entries change phase and how the activity eventually deactivates them.
base\quest\minor_quests\mq003\scenes\mq003_01_homeless.sceneHow 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.communityHow 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.entHow 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.appHow one appearance resource stores multiple internal definitions and a WomanAverage frame contract.
base\quest\side_quests\sq031\phases\sq031_rogue.questphaseHow 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.questphaseHow 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.questphaseHow 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

SymptomInspect first
Nothing spawns after ActivateRegistered block and sectors, quest prefab dependency, area/registry community identity, entry/phase spelling, AI-spot identities, and character record
Spawn wait never opensWhether activation addressed the same community, entireCommunity/count comparison, area streaming, and the requested entry phase
Actor exists but the scene cannot bind itScene acquisitionPlan, communityParams.reference, communityParams.entryName, and scene start ordering
Actor appears at the wrong place or poseAI-spot placement, exact full spot NodeRef, area spotNodeIds, registry spotNodeRefs, and workspot resource
Actor exists logically but is invisible or malformedCharacter record entity path, exposed entity appearance, internal .app name, frame/component graph, and saved appearance history
Actor is unexpectedly hostile or armedCharacter-record base, reaction/faction, action map, archetype, abilities, and equipment
Role command fires but the actor does not moveSpawn readiness, AI tier, command target, concrete typed params, path/workspot streaming, and navmesh
Actor walks back when the next activity startsFollower/patrol role was cleared before the next scene, defend, phase, or cleanup owner took over
Actor vanishes during the sceneCleanup edge ordering, scene outcome handoff, and the distance/trigger used before Deactivate
A clean resource change behaves like an older buildStarting 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:

PropertyResponsibility
communitiesDataHolds one or more worldCommunityRegistryItem records.
communityId.entityId.hashSelects the matching compiled community source.
entriesInitialStateGives each named entry its initial active state and initial phase.
templateEmbeds communityCommunityTemplateData for the entries available to this registry item.
workspotsPersistentDataRetains 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:

PropertyResponsibility
sourceObjectIdSupplies the community/source identity that joins the area to the registry item.
area.entriesData[].entryNameMirrors one registry entry CName.
phasesData[].entryPhaseNameMirrors one spawn phase CName.
timePeriodsData[].periodNameMirrors the selected period such as Day.
timePeriodsData[].isSequenceMirrors whether the period uses its spot set as a sequence.
timePeriodsData[].spotNodeIdsNames the world-global AI-spot identities used by the period.
streamingDistanceParticipates 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:

  1. record the full compiled-area NodeRef and its derived source identity;
  2. confirm the registry item’s communityId matches that source identity;
  3. derive the registry node identity from the full community NodeRef plus _registry, and confirm it is different;
  4. list each entry and initial phase once;
  5. derive each world-global AI-spot ID from that spot’s full NodeRef and list both forms;
  6. confirm the registry spotNodeRefs, area spotNodeIds, and persistent spot rows describe the same set;
  7. reject zero values and every source/registry/spot/sibling collision;
  8. confirm the block mounts both the AlwaysLoaded registry sector and the Quest area/spot sector;
  9. 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

SymptomInspect first
Activation node runs but no community appearsArea sector streaming, sourceObjectId/communityId equality, prefab root, and entry initial state
One community affects anotherReused community/source identity or copied vanilla identity
Loading near the area crashesRegistry-node collision, malformed handles, bad spot IDs, or another resource activated at the same boundary
Registry looks correct but the actor never gets a placeArea entriesData/phase/period mirror and exact spotNodeIds
Spot exists but is not persistent or enabled as expectedRegistry workspotsPersistentData and the spot’s world-global identity
Old behavior survives a corrected archiveReturn 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:

PropertyMeaning in the inspected shape
characterRecordIdTweakDB 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.
entryNameCommunity-local name used by activation and scene acquisition. It is a CName, not the actor’s display name.
phaseNameCommunity-local mode selected by initial state or a Spawn Manager action.
appearancesNames the spawn phase can request from the selected character/template chain.
hourSelects the authored time-period row. Day in one fixture is not a rule that communities only work during daytime.
isSequenceTells this period whether its spot list represents sequenced use in the inspected model.
quantityRequested quantity for that period; it is not a readiness timeout or scene performer count.
spotNodeRefsFull 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 templateCompiled area
communitySpawnEntry.entryNamecommunityCommunityEntrySpotsData.entryName
communitySpawnPhase.phaseNamecommunityCommunityEntryPhaseSpotsData.entryPhaseName
communityPhaseTimePeriod.hourcommunityCommunityEntryPhaseTimePeriodData.periodName
communityPhaseTimePeriod.isSequencecommunityCommunityEntryPhaseTimePeriodData.isSequence
communityPhaseTimePeriod.spotNodeRefscommunityCommunityEntryPhaseTimePeriodData.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.

ConcernOwner
World position and orientationThe spot node’s matching nodeData placement
Quest-prefab child identityFull/local NodeRef chain and QuestPrefabRefHash
Workspot behaviorAIActionSpot.resource depot path
Finite versus continuing useisWorkspotInfinite together with the community period design
Ordered movement between spotsA validated sequence using isSequence, multiple spot identities, and compatible AI behavior
Actor selectioncommunitySpawnEntry.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:

  1. the resource exists at the exact depot path;
  2. the spot transform leaves enough clearance for the actor;
  3. the surface is reachable and supports the intended ground contact;
  4. the actor does not clip into walls, props, or the player approach lane;
  5. the broad setup route streams the area before the scene needs the actor;
  6. each named pre-scene seed load in Cases 3, 4, and 7 reacquires the same passive contact without duplication;
  7. normal exit, post-contact_done reload, 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

SymptomInspect first
Entry activates but uses the wrong actorcharacterRecordId, entry name, registry item, and saved activation history
Actor is invisible or malformedRequested appearance, record-to-entity path, dependencies, and expansion requirements
Actor appears at an unexpected sibling spotRegistry spotNodeRefs, area spotNodeIds, and world-global ID uniqueness
Actor clips or faces awaySpot nodeData position/orientation, workspot semantics, clearance, and navmesh
Sequence never advancesisSequence, finite/infinite settings, spot ordering, AI behavior, and navigation evidence
Scene asks for an entry that exists only in another communityScene 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 scopeUse whenReview risk
Whole community (None / None)The lifecycle owner intentionally brings the configured community into its active stateIt may activate more entries than the scene needs
Named entry and phaseOne entry must move to one explicit authored phaseA 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:

FieldContract
acquisitionPlan: communityTells the scene to bind through the community system rather than spawn/despawn or context lookup
communityParams.referenceNames the community source the quest activated and waited for
communityParams.entryNameSelects the community-local entry the performer represents
actorIdSupplies the scene-local performer identity used by sections and events
actorNameSupplies 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:

  1. Confirm the registry item and streamed area use the intended community identity and that the area can stream on every supported approach.
  2. Add a Spawn Manager action using the community source NodeRef and chosen whole-community or named-entry scope.
  3. Add a separate CharacterSpawned pause condition. Inspect the comparison, count, entireCommunity, and object reference rather than relying on the node caption.
  4. Route readiness to a broad setup-area condition. Do not duplicate the activation action on every approach branch.
  5. Start the scene through the exact named input after readiness and setup are both satisfied.
  6. Set every community-backed scnActorDef to acquisitionPlan: community with the matching community reference and entry CName.
  7. 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.

RouteRequired observation
Case 1: outside original clone, ordinary approachActor materializes before scene acquisition; setup occurs once
Case 2: separate original-near-setup clone, immediate entrySpawned readiness still wins the race before setup advances
Cases 3/4/7: distinct clones of seed-pre-scene-outside-setupThe 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 cloneNo duplicate scene start or accidentally reactivated contact
Supplemental: generic fast travel, then approachSame ordering; no start-time acquisition failure
Supplemental: arbitrary pre-scene save point, then loadNo stale activation or skipped readiness edge; no duplicate setup or skipped readiness
Supplemental: complete alternate named outcomesEach 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

SymptomLikely contract failure
Spawn Manager completes but no actor appearsActivation 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 presentAggregate Greater 0 was mistaken for an all-performers condition
Readiness never opensWrong reference/scope, inactive entry, unavailable spot, bad area placement, or dirty save
Scene starts as V crosses the innermost trigger and immediately misbehavesBroad setup and narrow engage boundaries were collapsed
Actor is visible but scene has no performerWrong acquisition plan, community reference, entry name, or scene start context
Scene restarts on returnCompletion 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:

  1. route the named scene output into the questphase;
  2. write any outcome or choice fact the branch actually owns;
  3. advance or close the applicable journal objective so cleanup does not erase the only visible result;
  4. converge only branches that have equivalent cleanup requirements;
  5. wait for the safe spatial exit;
  6. issue the intended Deactivate action;
  7. complete the child/root branch according to its explicit output contract;
  8. 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.community uses 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 pointActionRequired observation
Clean pre-activation saveApproach, complete normal outcome, leave cleanup areaActor remains through named scene exit; progression writes; cleanup issues once
Clean pre-activation saveApproach, choose every alternate outcome, leaveEach 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 routeExact readiness/acquisition state resumes without a duplicate actor; the named case completes as specified
Arbitrary/unlisted active actor before sceneSave/load at another point, then enter broad setup areaSupplemental: readiness/acquisition resumes without duplicate actor or scene
During scene at a supported save pointSave/reload and finishPerformer 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 routeProgression is retained and delayed deactivation occurs once
Named seed-completed (Cases 9/11)Load a distinct byte-identical clone under the case-specific installationScene does not restart and no stale contact reactivates
Active contactTrigger intended interruption/combat casesNo 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

SymptomInspect first
Actor disappears before the scene finishesDeactivate edge, named-exit routing, workspot/AI ownership, and cleanup trigger polarity
Actor remains forever after completionWhether the cleanup-area Exited edge was armed, action scope/reference, and saved branch state
Scene restarts although the actor was removedDurable completion fact/journal gate and setup/start path
Different saves produce different actor availabilityUnique-character ownership, old community activation, scene state, death/appearance state, and starting-save history
Actor spawns but behaves like a hostile gang memberCharacter record packages, faction/attitude logic, AI commands, and interruption policy
Workspot evidence appears contradictoryInstalled 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 cited ep1\... entity;
  • WolvenKit 8.19.0;
  • ArchiveXL 1.27.0, RED4ext 1.30.0, and redscript 0.5.31 for the wider quest package; and
  • TweakXL 1.11.3 when adding a mod-owned Character.* 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

ValueType and ownerWhat it selects
Character.cqa_contactTweakDBID in TweakDBCharacter gameplay record
mod\cqa\characters\contact\contact.entResourcePath on the recordRoot entity template
cqa_contact_defaultCName on entTemplateAppearance.nameAppearance exposed by the entity
defaultCName on entTemplateAppearance.appearanceName and the .app definitionInternal appearance payload
mod\cqa\characters\contact\contact.appResourcePath on the entity mappingAppearance resource
Component names and bind namesCName values inside the appearance graphIndividual runtime components and attachment relationships
Community entry and phase namesCommunity-local CName valuesWhich actor definition and mode the community requests
Community and AI-spot referencesNodeRef / world identity valuesThe spawned instance’s world ownership and position
Scene actorIdScene-local IDPerformer 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 surfaceWhy it matters to a quest
$base inheritancePulls in a large behavior and interaction baseline; Quest_NPC_Base and a gang gameplay base are not neutral equivalents.
entityTemplatePathMust resolve to the intended mod-owned .ent or an explicitly accepted game dependency.
appearanceNameCan participate in the requested exposed appearance; it is not the internal .app definition by default.
reactionPreset, baseAttitudeGroup, affiliationInfluence passivity, hostility, reactions, and other systems outside the quest graph.
actionMap, archetypeData, abilitiesSupply gameplay AI actions and capabilities; assigning a quest role does not erase them.
equipment and lootCan arm the actor and create drops or rewards the quest did not intend.
voiceTag and audio fieldsJoin later spoken/audio systems; a visible actor is not proof that casting is correct.
display-name fieldsJoin localization/UI; they are not community or scene identities.
savable/priority behavior inherited from the baseCan 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:

PropertyContract
baseEntityTypeDeclares the expected entity/body frame such as ManAverage or WomanAverage.
definition nameMust match the entity mapping’s internal appearanceName.
components[]Authored component graph, including resources, bindings, enabled state, and visual settings.
compiledDataCooked/runtime representation associated with the appearance; do not assume an edit to the visible component list regenerated it.
resolvedDependencies and resource fieldsMust resolve to installed game assets or mod-owned paths that the package actually ships.
mesh appearance, chunk mask, bind and skinning valuesSelect 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:

  1. In WolvenKit’s Asset Browser, find the exact entity and appearance depot paths cited above. Confirm that the entity comes from ep1 and record that dependency.
  2. Extract or add them only to the comparison project. Open the .ent in the CR2W editor and inspect RootChunk, defaultAppearance, and appearances[].
  3. For one mapping, record the exposed name, internal appearanceName, and full appearanceResource path.
  4. Open that .app. Confirm RootChunk is appearanceAppearanceResource, inspect baseEntityType, and locate the exact internal definition by name rather than array position.
  5. 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.
  6. 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.
  7. 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:

  1. Open the mod-owned .ent and reduce the first test to one exposed appearance mapping.
  2. Set a unique exposed name, point appearanceResource at the mod-owned .app, and set appearanceName to the exact internal definition.
  3. Set defaultAppearance to that exposed name. Remove or rename stale shell mappings that can be selected accidentally.
  4. Open the mod-owned .app, confirm the frame-compatible baseEntityType, and give the one test definition the intended internal name.
  5. 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.
  6. 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 entityTemplatePath resolves to the packed .ent;
  • defaultAppearance is 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.

StartActionRequired observation
Clean pre-activation saveActivate only the character’s communityExactly one actor spawns with the intended exposed appearance and no invisible logical puppet
Same clean baseline cloneApproach from each supported routeCorrect LODs, materials, hair/clothing behavior, facing, collision, and no late component pop
Active actorStream out and returnSame record, appearance, equipment, and community entry return without duplication
Supported pre-scene saveReload, then start the sceneScene acquires the same actor; facial/body animation and voice casting remain coherent
Active actor in intended passive/combat casesExercise reactions and damage policyFaction, attitude, equipment, archetype, mortality, and loot match the quest design
Post-outcome saveReload, leave the cleanup boundary, revisitOutcome persists; actor releases once; no stale appearance or reactivation
Completed saveRemove the mod archive and load only a disposable cloneFailure 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

SymptomInspect first
Logical actor exists but is invisibleRequested exposed name, entity mapping, internal .app name, resource path, component enabled state, and compiled representation
Appearance from another NPC appearsRecord inheritance, appearanceName, entity default, community phase appearance, dirty-save history, and mapping-name collision
Body, clothing, or hair is malformedbaseEntityType, rig/skin target, bind names, garment support, chunk masks, companion components, and frame compatibility
Actor is unexpectedly hostile or armedRecord base, reaction preset, attitude/faction, action map, archetype, abilities, and equipment
Actor works until stream-out or reloadPersistent identity, community lifecycle, entity dependencies, LOD/proxy data, and clean starting save
Scene has the actor but face/body animation failsRig and facial setup, scene performer binding, animsets, voice/lipsync resources, and whether the body frame matches the authored performance
Package replaces a vanilla NPC globallyA 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

OwnerWhat it contributesWhat it does not prove
Character.* TweakDB recordReaction preset, faction/attitude, action map, archetype, abilities, equipment, stats, loot, and other gameplay defaultsA spawned instance, a route, or quest ownership
Community entry and phaseActor instance, requested appearance, schedule/quantity, and ambient AI-spot assignmentGameplay-tier readiness or scene acquisition
questPuppetAIManagerNodeDefinitionRequested story/AI tier for one or more entity referencesThat the actor spawned, navigated, or finished a role
questMiscAICommandNode and typed paramsA high-level scripted command such as assigning or clearing a roleNavmesh, path existence, arrival, or safe interruption
AI spot, spline, or workspotPlaced activity, route reference, animation/behavior resource, and transformActor 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 behaviorSmaller control surface
Actor spawns already standing/smoking/guardingCommunity phase plus placed worldAISpotNode
Quest orders a spawned actor into a specific placed activityquestUseWorkspotNodeDefinition plus separate state observation
Actor follows V through ordinary spaceGameplay tier plus AIFollowerRole
Actor traverses an authored splineGameplay tier plus AIPatrolRole and path parameters
Actor performs a sceneRelease 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:

  • CharacterSpawned before 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:

  1. observe that the actor reached the required boundary;
  2. prepare the next owner, including scene/workspot readiness;
  3. write any state needed to resume after reload;
  4. clear or replace the old role only when the next owner can take over;
  5. keep the community active until every scene, AI, combat, and workspot owner has released the actor; and
  6. 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:

  1. 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.
  2. In the mod project, confirm the actor’s community activation and CharacterSpawned gate already work on a clean save.
  3. Add a questPuppetAIManagerNodeDefinition. Add one entry, point its entityReference at the exact actor/community entry, and select aiTier: Gameplay for the reduced case.
  4. Add a questMiscAICommandNode. For follower or patrol assignment, set its concrete params handle to AIAssignRoleCommandParams, add the intended role subtype, and populate only the focused fields. Record the separate function value rather than inferring it from the subtype.
  5. For follower behavior, set followerRef to the intended target. For patrol, set the exact path NodeRef and explicitly review every path parameter.
  6. If the activity is a direct workspot instead, add questUseWorkspotNodeDefinition, populate entityReference, add questUseWorkspotParamsV1, and review all movement, teleport, infinite, interruption, combat, and output choices.
  7. Add separate actor-state conditions for route or activity acceptance. Keep any unproved role-finished/workspot condition Experimental.
  8. Add the next-owner handoff and a separate role-clear node using AIClearRoleCommandParams where the design requires it.
  9. Route every cut/interruption edge to a branch that releases ownership or deliberately preserves the actor. Never leave CutDestination semantics implicit.
  10. 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

StartActionRequired observation
Clean pre-activation saveActivate, wait readiness, apply Gameplay tierOne intended actor is ready before the behavior command; no duplicate or early command
Same baseline cloneAssign follower role in ordinary walkable spaceActor follows the intended target and actor-position gates, not player-position gates, open in order
Same baseline cloneRun the patrol/workspot routeExact path/spot resolves; movement and animation match every authored parameter
Role activeEnter and leave combat or another intended interruptionAuthored response occurs; command does not loop, vanish, or deadlock unexpectedly
Role active at a supported save pointSave/reload, then continueOne actor and one coherent owner resume; document whether the command must be reissued
Actor near a streaming boundaryStream out and returnCommunity, path/workspot, tier, and role recover according to the authored policy
Final route gateTransfer to scene, defend, next phase, or cleanupOld role is retained until the next owner is ready, then cleared exactly once
Completed saveReload and revisitNo 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

SymptomInspect first
Command fires but actor does not moveSpawn readiness, Gameplay tier, command target reference/names, concrete params type, navmesh, and streamed path/workspot
Wrong member of a community receives the rolegameEntityReference.reference, names[], entry spelling, and intended multiplicity
Actor follows briefly, then returns to the spawn spotRole cleared or replaced before the next owner; community ambient spot remained active
Patrol starts at an unexpected pointFull path NodeRef, continuation policy, startFromClosestPoint, point ordering, and actor spawn position
Patrol never completesisInfinite, loop/back-and-forth policy, path validity, and unproved completion observer
Actor teleports into or clips through the workspotteleport, movement type, spot transform, workspot resource, clearance, and frame/animation compatibility
Actor restarts the activity after combatrepeatCommandOnInterrupt, combat policy, durable quest state, and duplicate command path
Scene cannot acquire or animate the actorActive AI role/workspot ownership, scene community reference/entry, actor readiness, and missing handoff
Reload produces a different resultDirty save, saved community/actor state, command reissue policy, stale role, and installed archive filename
Node caption says clear while payload assignsInspect 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:

OwnerResponsibility in the First Contact example
Streaming sector and communityRegister #cqa005_com_contact, materialize the contact, and provide the scene marker #cqa005_sm_contact
.scene / scnSceneResourceAcquire actor 0 and V as actor 1, play one timed line, run a small PuppetAI branch, and expose contact_done
.questphase / questSceneNodeDefinitionResolve the soft scene path at the marker, enter through start, and receive contact_done
Journal and factsAdvance the meet/leave presentation after the scene returns, then record terminal completion after child cleanup
ArchiveXL localizationMerge the external subtitle and voiceover resources used by the spoken-line RUID
SaveRetain active quest, journal, fact, checkpoint, community, and scene state independently of a later archive edit

Reading route

Read these pages in order:

  1. Scene resource anatomy — the v5 root, typed empty stores, resource references, debug data, and interruption scaffold.
  2. Actors and performers — community acquisition, player context lookup, performer symbols, actor behavior, and lipsync slot cardinality.
  3. Screenplay, sections, and events — the separate line, event, section, duration, and ID domains.
  4. Entry, exit, and quest handoff — the exact four-node topology and the questSceneNodeDefinition interface.
  5. Author one spoken line — a bounded WolvenKit procedure for All clear. Keep moving. and its external subtitle/VO join.
  6. Cleanup and save state — interruption versus completion, named-outcome handling, community teardown, and clean-save acceptance.
  7. Choices, outcomes, and scene-local localization — native choice items, padded socket observations, embedded locStore joins, and multi-exit quest handoff.
  8. External VO, WEM, and lipsync — the parallel subtitle, audio, and actor-slot chains plus cardinality-first diagnosis.
  9. Animation events and scene workspots — animation-set visibility, timed cinematic events, scene workspot identity, and cleanup boundaries.
  10. Lab 5: First Contact — the complete resource inventory, ownership model, exact graphs, and evidence boundary.
  11. Author First Contact in WolvenKit — the field-level construction and round-trip procedure.
  12. 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.scene
  • base\quest\minor_quests\mq003\scenes\mq003_03_orbital_pod.scene
  • base\quest\minor_quests\mq007\scenes\mq007_01_gun_found.scene
  • base\quest\minor_quests\mq010\scenes\mq010_02_barry_talk.scene
  • base\animations\quest\minor_quests\mq007\anim\body\mq007__talking_gun__male_fpp.anims
  • base\workspots\common\wall\generic__stand_wall_lean_left__stand_around__01.workspot
  • base\localization\en-us\lipsync\base\quest\minor_quests\mq007\scenes\mq007_01_gun_found\skippy.anims
  • base\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

SymptomInspect first
Scene never beginsCommunity Activate, CharacterSpawned, world marker resolution, phase prefab scope, soft scene path, and start socket
Crash or failure during scene startupActor acquisition, readiness, lipsync ID-to-array cardinality, and missing referenced resources
Subtitle is absentScene line RUID, registered subtitle map, its subtitle-entry reference, and matching stringId
Subtitle works but audio is silentVO-map registration, matching stringId, both gender paths, WEM depot path, and audio logs
Choice labels are blank or staleScreenplay option locstring, unsigned locale-block ordering, descriptor/payload variantId, and vpeIndex
Choice takes the wrong branchOption order, output ordinal, destination, and stale active-scene state
Line plays but the quest does not continueScene exit name and node, scene-node output socket name, and outgoing questphase connection
Animation does not play or deforms the actorPerformer mapping, actor-visible animation-set slot, animation name, component/mask, and rig compatibility
Scene workspot never succeedsActor reference, workspot data/instance IDs, transform, params, wrapper mapping, and output semantics
Contact remains after completionLeave condition, community Deactivate, and any durable community/device state; reaching scnEndNode is not cleanup
A rebuild behaves like an older graphSave 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:

PropertyExact checkpoint value
idscnInterruptionScenarioId 0
nameDefault
enabled1
Interrupt conditionscnCheckSpeakersDistanceInterruptCondition; comparison Greater; distance 6
Return conditionscnCheckSpeakersDistanceReturnCondition; comparison Less; distance 5
playInterruptLine1
talkOnReturn1
forcePlayReturnLine0
playingLinesBehaviorDefault

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:

  1. confirm the root type, version, platform, and category;
  2. confirm every typed store and collection still has its native $type;
  3. compare actor, performer, graph-node, entry, exit, screenplay, event, and locstring IDs in their own domains;
  4. compare the lipsync array length with every actor’s referenced slot;
  5. compare both interruption conditions and their thresholds;
  6. compare graph edges, output and input stamps, startNodes, and endNodes;
  7. 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:

  1. activate the community at #cqa005_com_contact;
  2. wait on CharacterSpawned for the relevant entry or whole-community scope;
  3. complete any broad setup needed for visibility and workspot state;
  4. 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 roleActor IDPerformer IDDebug entity reference
Contact01#cqa005_com_contact, name contact
V1257#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 slot 0 is addressable;
  • actor 1 -> lipsync slot 0 is addressable;
  • actor 1 -> lipsync slot 1 would 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:

ActorbehaviorMode
0 contactOnlyIfAlive
1 VOnlyIfAlive

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:

PropertyExact value
screenplayLineIdscnscreenplayItemId 1
idscnSceneEventId 8646165628675208917
startTime0
duration2598 ms
visualStyleregular
voContextVo_Context_Quest
voExpressionVo_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 domainFirst Contact valueIdentifies
scnActorId0, 1Contact and V
scnPerformerId1, 257Debug performer symbols
scnNodeId1, 2, 3, 4Start, Section, End, PuppetAI wrapper
scnscreenplayItemId1The line definition and event-to-line join
scnSceneEventId8646165628675208917The timed event instance
scnlocLocstringId9638591835734011695External subtitle and voice lookup
scnLipsyncAnimSetSRRefId0Index 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 nodeSource output stampDestinationDestination input stamp
Start 1name 0, ordinal 0Section 2name 0, ordinal 0
Start 1name 0, ordinal 0PuppetAI wrapper 4name 0, ordinal 1
Section 2 normalname 0, ordinal 0End 3name 0, ordinal 0
Section 2 cancelname 1, ordinal 0no destination—
PuppetAI wrapper 4name 0, ordinal 0no destination—
End 3no 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:

PropertyValue
Entry targetContact entity reference beneath #cqa005_com_contact
aiTierCinematic
Quest socketsCutDestination, In, Out
Wrapper isockMappingsCutDestination, In
Wrapper osockMappingsOut
Wrapper output destinationsnone

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:

SocketSocket typeFirst Contact connection
CutDestinationCutDestinationUnconnected
startInputConnected from the child flow after actor readiness/setup
contact_doneOutputConnected to success-state and cleanup progression
Default INTOutputUnconnected
Default RETOutputUnconnected

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

RecordExact value
Review date2026-08-09
GameCyberpunk 2077 Windows GOG 2.31a
WolvenKit8.19.0
ArchiveXL1.27.0
RED4ext1.30.0
redscript0.5.31
Structural statusStructurally validated
In-game verificationFollow 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:

AreaStart checkpoint contract
RootscnSceneResource, version: 5, PLATFORM_PC, minorQuests
ActorsContact actor 0 from community entry contact; V actor 1 by findInContext
Debug symbolsPerformer 1 for the contact and 257 for V; other debug arrays explicitly empty
LipsyncBoth actors reference slot 0; one generic lipsync row exists
Public routesEntry start -> node 1; exit contact_done -> node 3
GraphStart 1 output 0/0 -> End 3 input 0/0; startNodes[1]; endNodes[3]
ScreenplayTyped scnscreenplayStore; lines: []; options: []
Embedded localizationTyped 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:

PropertyValue
itemIdscnscreenplayItemId(1)
speakerscnActorId(0)
addresseescnActorId(1)
locstringId.ruidunsigned 9638591835734011695
usage.playerGenderMask.mask3
Male/female lipsync animation nameNone

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 0 behavior OnlyIfAlive;
  • actor 1 behavior OnlyIfAlive;
  • one handled scnDialogLineEvent at startTime: 0;
  • screenplayLineId: scnscreenplayItemId(1);
  • unsigned event ID 8646165628675208917;
  • event duration 2598 ms;
  • sectionDuration: scnSceneTime(stu = 2998);
  • normal output stamp 0/0 to End 3 input 0/0;
  • cancel output stamp 1/0 with 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.

RoleCooked depot path
Subtitle mapmod\cqa\cqa005\localization\en-us\subtitles\cqa005_subtitles_map.json
Subtitle entriesmod\cqa\cqa005\localization\en-us\subtitles\cqa005_subtitles.json
VO mapmod\cqa\cqa005\localization\en-us\vo\cqa005_vo.json
Voice assetmod\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;
  • scnWorldMarker NodeRef #cqa005_sm_contact;
  • input start;
  • output contact_done;
  • unconnected CutDestination, Default INT, and Default RET routes;
  • 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:

  1. root type, v5/platform/category, typed empty containers, and exact native property names;
  2. actor acquisition and performer-symbol joins;
  3. lipsync reference array length and every slot index;
  4. screenplay item 1, event-to-item join, distinct unsigned event/RUID values, and 2598/2998 timing;
  5. graph IDs, all five output-socket states, all three connected edges, and both destination input stamps;
  6. startNodes[1], endNodes[3], and the named entry/exit;
  7. questphase socket names/types, soft depot path, and marker NodeRef;
  8. 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:

  1. the community activates and the contact becomes ready before scene launch;
  2. normal approach does not crash or race actor acquisition;
  3. the contact says All clear. Keep moving. once;
  4. the subtitle appears and the synthetic WEM plays;
  5. the scene reaches contact_done, not a nonexistent end route;
  6. the post-scene objective/pin handoff occurs once;
  7. leaving the cleanup area deactivates the contact, after which the child returns and the root writes its terminal fact;
  8. the named pre-scene seed loads in Cases 3/4/7 and the post-contact_done and 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

SymptomLikely first mismatch
Line has no textRUID 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 silentVO-map registration, VO stringId, gender WEM path, missing WEM, or incompatible audio asset
Crash near scene launchActor not ready or a lipsync index is not addressable after cooking
Scene exits immediatelyRoot/child start graph invoked the shell, or Section is bypassed by a retained Start-to-End edge
Scene never completesSection normal output misses End, End is absent from endNodes, or the quest expects end instead of contact_done
Line is clippedEvent 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

BoundaryNative ownerWhat it establishesWhat it does not establish
Section normal output.scene graphThe timed line section reached its normal socketA public named outcome or quest transition
End node 3.scene graphThe scene reached a terminating graph nodeCommunity deactivation or a persistent completion fact
Exit contact_done.scene + questSceneNodeDefinition interfaceThe child phase can continue through the matching outputCleanup has occurred
Child phase outputChild .questphaseMeet/leave objectives and community teardown completedThe root completion guard/fact has been updated
Fact cqa005_completedRoot .questphase and saveLater starts can take the already-completed routeJournal, 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:

  • CharacterSpawned prevents the scene racing a community entry that has not materialized;
  • contact_done is consumed before the meet objective and pin change;
  • the leave objective makes delayed cleanup visible to the player;
  • IsOutside avoids 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:

  • scnEndNode 3 and named exit contact_done;
  • the quest scene node’s unconnected Default INT and Default RET outputs;
  • its empty interruptionOperations;
  • its unconnected CutDestination;
  • the wider IsOutside cleanup 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 areaFirst Contact example
Factscqa005_completed and any prior probe values
JournalQuest/objective state, visited state, tracked entry, and mappin state
Quest graphActive root/child nodes and checkpoint progress
SceneActive or interrupted scene state and its return path
CommunityActivation, spawned entries, phase/workspot state, and deactivation progress
DevicesController 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.

CaseSave point and actionRequired observations
Normal routeClean save; approach, hear the line, leave the outer cleanup areaContact 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 routeNo duplicate contact or objective transition; actor reacquires; Cases 3/4 start once and Case 7 survives its stream-away/return
Arbitrary pre-scene save/loadSave at any unlisted active-child point, then loadRecord the exact behavior; do not infer it from the supplied seed-save cases
Active-scene reloadWhere saving is permitted, save during the line or interruption and reload; otherwise record the nearest game-provided checkpointNo crash, stale speaker, duplicate line, false success, or leaked Cinematic AI state; exact behavior is recorded rather than inferred
Interrupt and returnCross above 6 units during the line, then return below 5Default 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 routeMeet 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 conditionRoot guard or removal isolation behaves as frozen; final journal state does not reactivate the quest
Case 7 stream away and returnLoad its seed-pre-scene-outside-setup clone, leave the finite Quest descriptor by ordinary movement, return, and finishNodeRefs 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

SymptomInspect first
Contact vanishes during the lineCleanup branch reached too early, wrong trigger state/NodeRef, or an interruption route was conflated with deactivation
Contact remains after leavingIsOutside condition, community target/scope, Deactivate operation, or active save state from an older graph
Meet succeeds but leave objective never appearsConnection order after contact_done, journal socket names, and pin-inactive node
Quest finishes but contact remainsRoot fact/journal completion bypassed child cleanup, or deactivation occurred after rather than before child output
Quest restarts after completionCompletion fact write, root guard comparison/branch, or test save history
Behavior changes only after reloadActive quest checkpoint, scene interruption state, community phase, journal state, and installed build
Start checkpoint completes the meetingRoot 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.

RecordValue
Practical baselineCyberpunk 2077 Windows GOG 2.31a, WolvenKit 8.19.0, ArchiveXL 1.27.0, RED4ext 1.30.0, redscript 0.5.31
Focused vanilla comparisonObserved in vanilla
New mod-owned choice or multi-exit sceneExperimental 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
LayerNative ownerWhat it identifies
Screenplay optionscnscreenplayChoiceOptionOne choice item and its scnlocLocstringId
Choice presentationscnChoiceNodeOptionThe choice item shown by one choice node, plus its conditions and interaction styling
Localized labelscnlocLocStoreEmbeddedDescriptor-to-payload lookup for the option locstring
Local branchscnChoiceNode.outputSocketsWhich scene node receives the selected option
Quest resultscnExitPoint and the calling quest scene nodeWhich 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 rowScreenplay itemOutput stampFirst destination
Option 02name 0, ordinal 0node 48
Option 1258name 0, ordinal 1node 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:

ContentScene fieldText owner
Spoken linescreenplayStore.lines[].locstringIdExternal subtitle resource; VO is a separate external map/WEM branch
Choice labelscreenplayStore.options[].locstringIdThe 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_db block with en_us merely 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:

  1. each option socket reaches the intended branch;
  2. each branch reaches its own scnEndNode;
  3. every End is present in sceneGraph.endNodes;
  4. exitPoints maps a stable CName such as accept or refuse to that End;
  5. the calling questSceneNodeDefinition has an output socket with the exact same name;
  6. each quest output immediately writes the intended durable result fact;
  7. 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.

  1. In WolvenKit 8.19.0, extract one evidence-matched vanilla .scene for inspection. Do not add the extracted resource to your mod or redistribute its complete CR2W or serialized export.
  2. In a mod-owned v5 scene, allocate unique scnscreenplayItemId values for the options. The audited ordinary option pattern is 2 + 256n; treat that as Observed in vanilla, not an unchecked license to collide with existing items.
  3. Add one scnscreenplayChoiceOption per visible option. Set itemId, a full unsigned locstringId.ruid, and its usage fields deliberately.
  4. Add the matching scnChoiceNodeOption rows. Match each screenplayOptionId to one screenplay option and copy the remaining option semantics from the closest compatible vanilla case.
  5. Add one output stamped name 0 and ordinal 0..n-1 for 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.
  6. Populate locStore.vdEntries and vpEntries. Preserve typed IDs, locale-block structure, unsigned numeric ordering, duplicate descriptor relationships, vpeIndex, and matching payload variantId values.
  7. Build each branch as ordinary scene nodes and sections. Decide whether it rejoins locally or reaches a distinct End and named exit.
  8. If the quest consumes several exits, add exact matching output sockets to its questSceneNodeDefinition and write one durable result on each route.
  9. 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.
  10. 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

CaseRequired observation
Every option from the untouched pre-scene saveCorrect label, branch, spoken response, named exit, and result fact
Optional branch then progression choiceOptional branch does not consume or duplicate the required progression choice
Save before the choice appearsReload presents one coherent choice group and no stale label
Save after selection, before named exitReload resumes the selected branch without reopening another branch
Save immediately after named exitQuest writes exactly one result and does not launch the scene again
Interrupt before selectionReturn behavior is coherent and no result is written
Interrupt after selectionBranch and return behavior match the declared design; no other outcome fires
Missing-locale negative controlDeliberately recorded fallback behavior; never inferred from the editor caption
Completed-save reloadChoice, scene, reward, and branch-only effects do not repeat
Mod removal from a disposable saveNo 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

SymptomInspect first
Option row is present but label is blankScreenplay option locstring, locale descriptor coverage, vpeIndex, descriptor/payload variantId, and block order
One option shows another option’s textUnsigned numeric sort, duplicate descriptor grouping, stale locStore payload, or an option pointing at the wrong locstring
Editor caption is correct but game text is wrongEmbedded locStore; caption is not the display source
Selecting option B follows branch AOption array order, output name 0 ordinal, destination node, and stale active-scene save
Choice appears but cannot progressChoice type/conditions, missing output edge, required persistent-line behavior, or a branch section that never ends
Scene reaches End but quest stallsEnd missing from endNodes, missing/wrong exitPoints name, or quest scene-node socket mismatch
Correct scene branch, wrong later debriefDurable result was not written immediately after the named output
Rebuild still shows old optionsStarting 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.scene
  • base\quest\minor_quests\mq003\scenes\mq003_03_orbital_pod.scene
  • base\quest\minor_quests\mq007\scenes\mq007_01_gun_found.scene
  • base\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.

RecordValue
Practical baselineCyberpunk 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 chainStructurally validated
Focused vanilla lipsync slots and localized assetsObserved in vanilla
Exact Lab 5 playbackVerify with the Lab 5 subtitle, audio, reload, and cleanup cases
Arbitrary WEM encoding and custom lipsync generationExperimental

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
QuestionCorrect 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:

RoleExact depot path
Subtitle mapmod\cqa\cqa005\localization\en-us\subtitles\cqa005_subtitles_map.json
Subtitle entriesmod\cqa\cqa005\localization\en-us\subtitles\cqa005_subtitles.json
VO mapmod\cqa\cqa005\localization\en-us\vo\cqa005_vo.json
Synthetic voice assetmod\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 domainPurpose
scnscreenplayItemIdJoins the timed dialog event to the screenplay line
scnSceneEventIdIdentifies that timed event instance
scnlocLocstringId.ruidJoins line, subtitle entry, and VO entry
WEM depot pathLocates audio bytes selected by the VO entry
scnLipsyncAnimSetSRRefId.idIndexes 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.anims
  • base\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:

CheckWhy it matters
Selected clip existsA valid animset slot does not guarantee a matching line clip
Clip and audio durations agreeInherited donor motion can extend a short clip
Facial keys survive importDuration alone says nothing about mouth movement
Skeletal reference survives importRemoving unwanted body motion must not remove the additive reference pose
Rig bone and facial-track ordering agreeA 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:

  1. Line scheduling: confirm the section reaches the scnDialogLineEvent, its screenplayLineId resolves, and the duration does not clip the line.
  2. Subtitle join: confirm the exact unsigned RUID, subtitle-map registration, subtitle-entry path, locale, and matching stringId.
  3. VO join: confirm VO-map registration, exact locVoLineEntry.stringId, selected gender path, archive presence, path case, and game/audio logs.
  4. 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.
  5. Actor readiness: confirm the intended actor was acquired and the event’s performer maps to it.
  6. Lipsync cardinality: inspect the cooked/round-tripped root collection and every selected slot, not only the raw source rows.
  7. Presentation compatibility: only after startup and lookup are stable, evaluate mouth movement, emotion, gesture, rig fit, and interruption.

This ordering distinguishes common boundaries:

ObservationMost likely branch to inspect first
Subtitle absent and audio silentLine event/RUID or both localization registrations
Subtitle present, audio silentVO map, selected WEM path, WEM presence/bytes, audio logs
Audio present, mouth motion wrongLipsync resource, actor slot, selected clip name, rig compatibility, line-specific animation data
Head stretches only during speechAdditive skeletal reference and rig/track ordering in the cooked clip; compare with the idle appearance
Crash at scene launch before text/audioActor acquisition and cooked lipsync slot addressability
Correct first line, later line silentLater line RUID, VO entry, WEM path/bytes, event timing
Works for one performer configuration onlyGender path, actor identity, slot/resource assignment, or rig compatibility

Manual WolvenKit authoring procedure

  1. 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.
  2. 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.
  3. Create a locVoiceoverMap. Add one locVoLineEntry with the same unsigned stringId and deliberate femaleResPath and maleResPath values.
  4. 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.
  5. 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.
  6. 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.
  7. 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.
  8. 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.
  9. 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

CaseRequired observation
Normal line from untouched pre-scene saveOne subtitle, one audio playback, correct speaker, no launch crash
Every distinct line RUIDCorrect text and WEM; no stale prior line
Every supported gender pathCorrect path selection and asset, or documented shared-path behavior
Every actor slotIn-range after cooking; intended resource and acceptable facial result
Save before scene launchReload starts one coherent scene and resolves current assets
Save between linesIf permitted, no duplicate/omitted line or stale lipsync state
Interrupt and returnAudio, subtitle, actor tier, and lipsync recover without a false named exit
Stream away and returnActor/resources reacquire without duplicate playback
Reload after named exitNo repeated VO or retained Cinematic AI/lipsync ownership
Missing-WEM negative control in a disposable candidateExpected 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.

RecordValue
Practical baselineCyberpunk 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 shapesObserved in vanilla
Lab 5 empty animation collections and scene-local PuppetAI branchStructurally validated
New cinematic animation, camera, look-at, or scene-workspot routeExperimental

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 behaviorNative mechanismPrimary completion/lifetime owner
Play a timed body clip inside a sectionscnPlaySkAnimEventEvent timing plus the owning section
Transition facial or idle statescnChangeIdleAnimEventEvent timing and subsequent reset/transition events
Aim eyes/head/body at a targetscnLookAtEventStart/stop request data and later scene events
Override player-camera parametersscneventsCameraParamsEvent or a compatible camera event familyEvent duration, reset flags, and scene interruption/exit
Put an actor into a scene-local workspotscnQuestNode wrapping questUseWorkspotNodeDefinitionWorkspot node outputs, actor AI, instance placement, and cleanup
Keep a community NPC at a world AI spot outside the sceneCommunity plus worldAISpotNode and .workspotCommunity 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 propertyObserved value
PerformerscnPerformerId(257)
Componentbody
Animation namestand__equip__skippy__01
Event IDunsigned 2159138510383108096
Start/duration0 / 2600 ms
Body-part maskspine_upper_body
Root motion enabled0
Blend-in / blend-out0.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 familyFields to audit firstCommon missing boundary
Play skeletal animationperformer, component, anim name, set visibility, start/duration, blend, root motion, maskRig/asset compatibility and exit pose
Change idle/facial stateperformer, facial keys, transition, duration, enabled stateExplicit transition/reset on every exit
Look at targetsource performer, target performer/actor/prop, target slot, start/stop request, transition and limitsClearing or replacing the request after branch/interruption
Camera parametersplayer-camera flag, camera ref, FOV/DOF overrides, duration, reset flagsRestoring 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:

LayerExact observed value
External workspot resourcebase\workspots\common\wall\generic__stand_wall_lean_left__stand_around__01.workspot
scnSceneWorkspotDataId4010771317
scnSceneWorkspotInstanceId241
Wrapper scene nodescnQuestNode 241
Inner native nodequestUseWorkspotNodeDefinition, ID 241
Actor targetentity reference #mq010_com_barry, name barry
Params typescnUseSceneWorkspotParamsV1
Workspot outputsWork Started, Success
Debug symbolworkspot 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

  1. Extract an evidence-matched vanilla scene and the referenced .anims metadata from your own installation for inspection. Cite the asset; do not redistribute it.
  2. Confirm the target actor’s rig, perspective, component, body type, and acquisition route match the comparison closely enough to justify a test.
  3. In the mod-owned scene root, add one scnCinematicAnimSetSRRef to cinematicAnimSets with the exact soft depot path and deliberate override/priority values.
  4. At the matching collection slot, add scnAnimSetAnimNames containing only the animation names expected from that set. Add the slot’s scnCinematicAnimSetSRRefId to the intended actor’s bodyCinematicAnimSets.
  5. Add one scnPlaySkAnimEvent to a section. Assign a unique full unsigned scnSceneEventId, the correct performer, component, animation name, start/duration, blend, mask, and root-motion settings.
  6. Size the section and subsequent transition deliberately. Add required reset/idle/look-at/camera events rather than assuming End cleans them up.
  7. 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

  1. Add a scnWorkspotData_ExternalWorkspotResource row with a unique scnSceneWorkspotDataId and evidence-matched .workspot depot path.
  2. Add one scnWorkspotInstance whose dataId selects that row. Allocate a unique typed instance ID, then set its origin marker and local transform.
  3. Add/update the corresponding scnWorkspotSymbol debug row if the chosen vanilla-compatible shape uses it.
  4. Add an scnQuestNode wrapper around questUseWorkspotNodeDefinition. Target the acquired actor exactly and use scnUseSceneWorkspotParamsV1.workspotInstanceId to select the instance.
  5. 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.
  6. 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.
  7. 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

CaseRequired observation
Normal approach from untouched saveActor reaches expected start state; event/workspot starts once
Animation start and end framesNo T-pose, snap, clipping, wrong body component, or truncated blend
Root-motion routeFinal transform and facing match the design without collision/navmesh escape
Workspot entry from several directionsNavigation/teleport policy, local transform, facing, and clearance are coherent
Branch bypasses the eventNo animation, look-at, camera, or workspot state leaks from an unentered branch
Interrupt during event/workspotActor, AI tier, camera, look-at, and graph route recover deliberately
Return after interruptionBeat resumes or restarts exactly as specified; no duplicate side effect
Save/reload during the beat, if permittedNo stuck pose, missing actor, duplicate event, or false Success
Stream away and returnCommunity actor and scene workspot reacquire without duplicate ownership
Named scene exitCamera/look-at/AI/workspot state is released before the quest cleans up the actor
Completed-save reload and mod removalNo 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

SymptomInspect first
Event runs but actor does not movePerformer mapping, actor-visible set slot, animation name, component/mask, and asset compatibility
T-pose or severe deformationRig/body/perspective mismatch, wrong animation set, missing linked channels
Animation is clippedEvent start/duration, section duration, blend-out, and following transition
Actor snaps or ends in the wrong placeRoot-motion enabled/mode, origin marker, offset, scene marker, and collision
Look-at persists after the lineMissing stop/replacement request or untested interruption/exit cleanup
Camera remains constrainedReset flags/events, player-camera ownership, named exit, and interruption path
Actor never enters workspotActor target, data-to-instance join, instance transform, entry params, navigation, streaming
Work Started fires but scene stallsWrong output consumed, Success semantics, infinite/finite params, or missing wrapper mapping
Workspot works once but fails after reloadSaved scene/community/workspot state, retained actor role, stale instance identity
Actor disappears at scene exitCommunity 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.scene
  • base\animations\quest\minor_quests\mq007\anim\body\mq007__talking_gun__male_fpp.anims
  • base\quest\minor_quests\mq010\scenes\mq010_02_barry_talk.scene
  • base\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.

RecordValue
Structural validation date2026-08-09
Runtime test dateNot 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

ComponentExact version
Cyberpunk 2077 for Windows (GOG)2.31a (public patch 2.31)
WolvenKit8.19.0
ArchiveXL1.27.0
RED4ext1.30.0
redscript0.5.31

Other versions may serialize different wrappers or produce different runtime behavior.

Prerequisites and downloads

Complete Lab 4, then read:

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

#ArtifactDepot pathOwner or purpose
1Root questphasemod\cqa\cqa005\phases\cqa005.questphaseRegistration, one-shot guard, root prefab, child call, quest completion
2Contact child questphasemod\cqa\cqa005\phases\cqa005_contact.questphaseMeet, readiness, scene handoff, leave, and cleanup lifecycle
3Scenemod\cqa\cqa005\scenes\cqa005_first_contact.sceneActors, screenplay item, line event, graph, entry, and named exit
4Journalmod\cqa\cqa005\journal\cqa005.journalQuest, phase, two objectives, and contact map pin
5Onscreen localizationmod\cqa\cqa005\localization\en-us\onscreens\cqa005_onscreens.jsonQuest, objective, and map-pin text
6Subtitle mapmod\cqa\cqa005\localization\en-us\subtitles\cqa005_subtitles_map.jsonRegistered route to the subtitle entries resource
7Subtitle entriesmod\cqa\cqa005\localization\en-us\subtitles\cqa005_subtitles.jsonMale and female text for locstring 9638591835734011695
8VO mapmod\cqa\cqa005\localization\en-us\vo\cqa005_vo.jsonMale and female audio lookup for the same locstring
9WEMmod\cqa\cqa005\localization\en-us\vo\contact_i_85c3283507e7ef2f.wemPrebuilt synthetic spoken audio
10Streaming blockmod\cqa\cqa005\world\cqa005_first_contact.streamingblockQuest and AlwaysLoaded sector descriptors
11Quest sectormod\cqa\cqa005\world\cqa005_first_contact.streamingsectorSetup/cleanup triggers, AI spot, and compiled community area
12AlwaysLoaded sectormod\cqa\cqa005\world\cqa005_always_loaded.streamingsectorScene 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

IDRED node typeDecisive payload
0questInputNodeDefinitionRoot In1 interface
1questOutputNodeDefinitionTerminating Out1
10questConditionNodeDefinitioncqa005_completed Equal 0
11questJournalNodeDefinitionSet First Contact quest Active
12questPhaseNodeDefinitionSoft cqa005_contact.questphase; null inline graph; empty instance prefabs
13questJournalNodeDefinitionSet First Contact quest Succeeded
14questFactsDBManagerNodeDefinitionSet 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

IDRED node typePurpose
0questInputNodeDefinitionReceive In1
1questOutputNodeDefinitionReturn terminating Out1
10questJournalNodeDefinitionActivate meet objective
11questMappinManagerNodeDefinitionActivate contact pin
12questSpawnManagerNodeDefinitionActivate contact/default
13questPauseConditionNodeDefinitionWait CharacterSpawned Greater 0, entireCommunity: 1
14questPauseConditionNodeDefinitionWait player IsInside #cqa005_tr_setup
15questCheckpointNodeDefinitionCreate the pre-scene checkpoint
16questSceneNodeDefinitionEnter start; continue only from contact_done
17questJournalNodeDefinitionSucceed meet objective
18questMappinManagerNodeDefinitionInactivate contact pin
19questJournalNodeDefinitionActivate leave objective
20questPauseConditionNodeDefinitionWait player IsOutside #cqa005_tr_cleanup
21questSpawnManagerNodeDefinitionDeactivate with entry/phase None for the whole community
22questJournalNodeDefinitionSucceed 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:

  1. the compiled area’s sourceObjectId equals the registry item’s communityId;
  2. the registry node placement has a different numeric world identity;
  3. the AI spot’s global ID is repeated by the compiled area’s spotNodeIds and registry workspotsPersistentData.

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:

OwnerCenterGeometry
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

SymptomCheck first
Quest never appearsRoot registration, archive pair, ArchiveXL and RED4ext logs, clean starting save
Objective appears but contact does notQuest-sector streaming, community/source join, entry and phase names, spot ID joins, CharacterSpawned payload
Contact spawns hostileGeneric character record, AI state, absence of unrelated combat stimuli, exact installed build
Contact exists but scene does not startSetup trigger, scene marker, actor acquisition reference, named start socket
Mouth moves but there is no subtitle or soundLocstring equality across scene, subtitle entries, and VO map; registered map paths; archived WEM path
Line plays but quest stallsScene exit contact_done, End 3, quest scene-node output name, unconnected default outputs
Contact disappears in viewCleanup IsOutside radius and deactivation order
Completed save starts againcqa005_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.

RecordValue
Structural validation date2026-08-09
Runtime test dateNot yet recorded

Required environment and clean-save rule

ComponentExact version
Cyberpunk 2077 for Windows (GOG)2.31a (public patch 2.31)
WolvenKit8.19.0
ArchiveXL1.27.0
RED4ext1.30.0
redscript0.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

  1. Download and extract the start ZIP.

  2. Make an untouched backup of the extracted checkpoint, then make a separate working copy. Keep WolvenKit closed while changing project identity.

  3. In the working copy, rename CQA_Lab05_FirstContact_Start.cpmodproj to CQA_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>
    
  4. Rename source\resources\CQA_Lab05_FirstContact_Start.archive.xl to source\resources\CQA_Lab05_FirstContact.archive.xl. Its YAML content and six registration roots do not change.

  5. Confirm the working copy has no _Start.cpmodproj and no _Start.archive.xl, then open CQA_Lab05_FirstContact.cpmodproj with File > Open Project. Reopen WolvenKit if it had cached the old project name.

  6. Confirm source\archive and source\raw each contain the eleven CR2W paths in the overview.

  7. Confirm the archived WEM exists at source\archive\mod\cqa\cqa005\localization\en-us\vo\contact_i_85c3283507e7ef2f.wem.

  8. Confirm the renamed loose ArchiveXL file is under source\resources and the source WAV plus provenance are outside source.

  9. 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:

ResourceDecisive check
Root phaseThree-node Input -> external Phase -> Output shell and one root prefab
Child phaseTwo-node Input -> Output shell and no phase prefabs
SceneStart 1 -> 3 End shell, actors, entry/exit, typed empty screenplay
JournalQuest, phase, meet, leave, and nested map-pin entries
Onscreen stringsFour secondary keys for title, objectives, and pin
Subtitle mapOne quest group entry pointing to the subtitle entries path
Subtitle entriescqa005_subtitles.json; one unsigned string ID with identical male/female text
VO mapThe same ID with identical male/female WEM paths
Streaming blockOne finite Quest descriptor and one AlwaysLoaded descriptor
Quest sectorSetup trigger, cleanup trigger, AI spot, compiled community area
AlwaysLoaded sectorScene marker, map marker, community registry
WEMRIFF/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 artifactNative reference owner
Contact childRoot questPhaseNodeDefinition.phaseResource
SceneChild questSceneNodeDefinition.sceneFile
Subtitle entriesRegistered subtitle map entry
WEMRegistered VO-map line entry
Two sectorsRegistered 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:

DomainCanonical unsigned valueDerivation source
Community/source5948510988927765319$/mod/cqa/cqa005/#cqa005_pr_first_contact/#cqa005_com_contact
Registry node6908684691797323855Full community NodeRef above plus _registry
AI spot global ID15950783814303760596$/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:

  1. compiled-area sourceObjectId.hash equals registry-item communityId.entityId.hash;
  2. the registry placement identity differs from that source identity;
  3. compiled-area spotNodeIds[0] equals workspotsPersistentData[0].globalNodeId;
  4. the registry template’s spotNodeRefs[0] names the full placed AI spot;
  5. 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:

NodePositionOutline
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 keyEnglish value
cqa_cqa005_titleFirst Contact
cqa_cqa005_objective_meetMeet the contact.
cqa_cqa005_objective_leaveLeave the meeting area.
cqa_cqa005_mappin_contactFirst 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:

PropertyExact value
itemId1
speakeractor 0
addresseeactor 1
locstringId.ruid9638591835734011695
usage.playerGenderMask.mask3

Leave male and female lipsync animation names at None. They are not WEM paths.

In the new section’s events, add one scnDialogLineEvent:

PropertyExact value
Event ID8646165628675208917
screenplayLineId1
startTime0
duration2598 ms
VO contextVo_Context_Quest
VO expressionVo_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;
  • scnQuestNode 4, wrapping one questPuppetAIManagerNodeDefinition entry for the contact at AI tier Cinematic.

Wire exactly three destinations:

Source stampDestination stampMeaning
Start 0/0Section 0/0Begin the line section
Start 0/0PuppetAI 0/1Fire the parallel AI-tier operation
Section 0/0End 0/0Terminate after the line section

Retain these empty outputs:

  • Section cancel output stamp 1/0 has no destinations;
  • PuppetAI output stamp 0/0 has no destinations;
  • End has outputSockets: [] and type: 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: one Activate action for community #cqa005_com_contact, entry contact, phase default;
  • node 13: questCharacterSpawned_ConditionType, Greater 0, entireCommunity: 1, same community reference;
  • node 14: player IsInside #cqa005_tr_setup, with isPlayerActivator: 1;
  • node 15: checkpoint debug string cqa005_first_contact, with endGameSave, ignoreSaveLocks, pointOfNoReturn, retryOnFailure, and saveLock all 0;
  • node 16: soft scene file, scnWorldMarker #cqa005_sm_contact, interruptionOperations: [], freeze/reapply/music flags 0, and exactly the five sockets described above;
  • node 20: player IsOutside #cqa005_tr_cleanup, also with isPlayerActivator: 1;
  • node 21: one Deactivate action with entry and phase both None, meaning the whole referenced community;
  • node 22: succeed the leave objective before returning Out1.

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:

NodeStateclassNamerealPath
10ActivegameJournalQuestObjectivequests/minor_quest/cqa005/cqa005_01/cqa005_01_obj_meet
17SucceededgameJournalQuestObjectivequests/minor_quest/cqa005/cqa005_01/cqa005_01_obj_meet
19ActivegameJournalQuestObjectivequests/minor_quest/cqa005/cqa005_01/cqa005_01_obj_leave
22SucceededgameJournalQuestObjectivequests/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

  1. Save every changed resource.
  2. Close and reopen the project; inspect both phase graphs and the scene.
  3. Use WolvenKit’s project build/package action.
  4. Confirm the packed archive and loose .archive.xl have the project name CQA_Lab05_FirstContact.
  5. Confirm the package contains eleven CR2W depot paths plus the exact WEM.
  6. Confirm the child, scene, subtitle entries, WEM, and sectors are archived even though they are not direct ArchiveXL roots.
  7. 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

SymptomInspect
WolvenKit rejects a community sectorHandle ownership, typed IDs, nodeData count/index, source/registry/spot collision
Scene reopens with missing endpointsstartNodes[1], endNodes[3], entry/exit node IDs, End type
Line resource cooks but lookup is incompleteSame unsigned ID in scene/subtitle/VO, map soft path, WEM depot path
External child looks valid but is unresolvedChild archived, root soft path, no extra root registration, matching In1/Out1
Built game archive contains the WAVMove authoring provenance outside project source; only the WEM belongs in the game archive
Start project invokes a lineRestore 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
RecordValue
Structural validation date2026-08-09
Runtime test dateNot 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

ComponentExact version
Cyberpunk 2077 for Windows (GOG)2.31a (public patch 2.31)
WolvenKit8.19.0
ArchiveXL1.27.0
RED4ext1.30.0
redscript0.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:

BoundaryCandidate geometryTest role
Setup25 m circumradius, 16 points, 12 m highScene may advance only after spawned readiness and player entry
Cleanup110 m circumradius, 20 points, 16 m highCommunity 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.

  1. Make CQA005 ORIGINAL OUTSIDE SETUP outside the setup volume on the verified ordinary approach.
  2. Make the separate CQA005 ORIGINAL NEAR SETUP near 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.
  3. Record each complete slot-directory name in your test notes.
  4. 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 cqa005 candidate. 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.
  5. 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.
  6. 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.
  7. 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.dat without the rest of its slot directory.

The test uses five named save captures:

Capture IDExact labelSource / parentFrozen observable stateExecution cases
original-outside-setupCQA005 ORIGINAL OUTSIDE SETUPExternal original / noneOutside setup on the ordinary route; never loaded any CQA Lab 1–5 candidate1, 10
original-near-setupCQA005 ORIGINAL NEAR SETUPSeparate external original / noneNear but outside setup; never loaded any CQA Lab 1–5 candidate2
seed-pre-scene-outside-setupCQA005 SEED PRE SCENE OUTSIDE SETUPCase 1 / original-outside-setupMeet Active; pin Active; child active; contact spawned and passive; V outside setup; checkpoint cqa005_first_contact not executed; scene not started; fact 03, 4, 7
seed-post-contact-inside-cleanupCQA005 SEED POST CONTACT INSIDE CLEANUPCase 1 / pre-scene seedcontact_done returned; Meet Succeeded; pin Inactive; Leave Active; child and contact active; V inside cleanup; fact 05, 6, 8
seed-completedCQA005 SEED COMPLETEDCase 1 / post-contact seedLeave and quest Succeeded; pin Inactive; contact deactivated; child and root returned; fact 19, 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.

  1. Close the game.

  2. Open the completed project in WolvenKit and use its normal build/package and install workflow.

  3. Confirm the start checkpoint, every older cqa005 archive, 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.

  4. From the game directory, run:

    Get-ChildItem .\archive\pc\mod\CQA_Lab0*.archive* |
      Select-Object -ExpandProperty Name
    

    The complete output must be exactly these two names, with no Start variant and no CQA_Lab01_* through CQA_Lab04_* pair:

    CQA_Lab05_FirstContact.archive
    CQA_Lab05_FirstContact.archive.xl
    
  5. 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.

  6. 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:

  1. 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, checkpoint cqa005_first_contact has not executed, the scene has not started, and cqa005_completed is 0.
  2. CQA005 SEED POST CONTACT INSIDE CLEANUP, after contact_done returns but before leaving cleanup. Meet is Succeeded, pin Inactive, Leave Active, child/contact active, and the fact remains 0.
  3. CQA005 SEED COMPLETED, after cleanup and root return. Leave and quest are Succeeded, pin Inactive, contact deactivated, child/root returned, and the fact is 1.

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 9638591835734011695 and contact_i_85c3283507e7ef2f.wem where 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.

ObservationClassification and first checks
Quest never appearsFailed registration/root activation; inspect exact pair, ArchiveXL log, root depot path, and untouched-save history
Meet/pin appears but no contactFailed streaming/community join; inspect finite sector, source/registry/spot IDs, entry/phase, workspot, and NodeRef logs
Contact is hostile without provocationFailed passive-contact case; retain surrounding stimuli and inspect the generic record/AI policy before changing the matrix
Immediate sprint deadlocks or scene starts without contactFailed readiness/setup ordering; inspect child nodes 13 -> 14 -> 15 -> 16 and the fast run’s streaming logs
Exact subtitle appears without soundFailed VO path/WEM lookup; inspect RUID, cqa005_vo.json, WEM depot path/hash, archive inventory, and audio logs
Sound plays without the exact subtitleFailed subtitle-map chain; inspect registered map, indirect entries path, both variants, RUID, and localization logs
Line/subtitle plays twiceFailed exactly-once scene lifecycle; inspect duplicate registration, scene start, saved state, and setup replay
Scene ends but meet does not succeedFailed named handoff; inspect contact_done, End 3, child output socket, and inert defaults
Reload replays the scene or pinFailed save-backed owner transition; retain the derived save before inspecting scene/journal state
Return after streaming creates a second contactFailed registry/area lifecycle or duplicate mount; inspect identities, descriptor ownership, and installed candidates
Contact disappears while V is inside cleanupFailed cleanup timing/geometry; inspect trigger polarity, route, node 20, and deactivation order
Completed reload starts the childFailed completion guard/write order or dirty candidate/save history
Removal run still mounts Lab 5Failed removal isolation; search for duplicate archive pairs and preserve fresh framework logs
Packaged project contains a tweak or script mutationReject the candidate; Lab 5 may reference the generic record but must not override or mutate it
A useful log was overwrittenRepeat 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

ResultParticle drawerEntity registrationPlacement
Shapes over V’s viewCParticleDrawerScreenPlayer entity templatesScreen rendering; test first and third person separately
Shapes above an NPCCParticleDrawerBillboardThe NPC’s actual entity templateA 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

  1. Particles, textures, and materials: make one readable, transparent image before adding animation.
  2. Effect timelines: schedule the intro, loop, outro, and optional sound.
  3. Entity spawners and status effects: connect the effect name to the target entity and trigger it through a native status.
  4. 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:

FileOwns
mod\cqa\effects\optics_v.effectV’s presentation timeline
mod\cqa\effects\optics_npc.effectNPC presentation timeline
mod\cqa\effects\particles\v_intro.particleIntro emitter and material for V
mod\cqa\effects\textures\intro_d.xbmColor image
mod\cqa\effects\textures\intro_m.xbmIndependent opacity mask
mod\cqa\effects\registration\optics_v.entPlayer spawner component
mod\cqa\effects\registration\optics_npc.entNPC spawner component
CQA_Effects.archive.xlEntity patch targets and onscreen-text registration
r6\tweaks\cqa_effects.yamlDuration, 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 pathInspect it for
base\fx\player\status_effects\blindness\p_blindness.particleScreen drawer, emitter modules, material, and compiled particle data
base\fx\player\p_hacking_glitch\p_hacking_glitch_low.effectA worldEffect resource and its track/event relationships
engine\materials\particles_generic.remtTextured particle material parameters
base\characters\entities\player\player_wa_fpp.entPlayer components and effect-slot availability
base\characters\entities\player\player_ma_fpp.entThe 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.

PropertyQuestion to answer
modules[].lifeTimeHow long can a spawned particle live?
modules[].size and scaleHow large is the particle?
particleDrawerIs it a screen image or a world-space billboard?
lods[].birthRateAre more particles emitted over time?
lods[].burstList[].spawnCountHow many spawn in a burst?
emitterLoopsCan the emitter repeat?
renderResourceBlobWhich 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

  1. Extract the particle reference into your inspection project. Inspect its drawer, modules, compiled data, and material before changing anything.
  2. 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.
  3. Draw a simple shape on a transparent RGBA canvas. Use a large, clear glyph before adding tiny details or fragment animation.
  4. Import the PNG through WolvenKit’s texture import workflow. Confirm that the resulting .xbm exists under source\archive at the intended path. Reopen or export it to check that the alpha channel survived.
  5. Open the particle’s localMaterialInstance. Inspect its baseMaterial and set the texture parameters to your imported assets.
  6. Keep the drawer compatible with the presentation. First test V using the screen drawer; make the NPC variant separately.
  7. Connect the particle to an effect track, then register and trigger it.
  8. 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 valuePurpose
DiffuseColor texture .xbm reference
AlphaMaskOpacity-mask .xbm reference
UseAlphaMaskEnables mask use in this material setup
ColorMaterial tint applied to the texture
ColorMultiplierColor intensity; start conservatively
AlphaGlobalOverall opacity
AdditiveAlphaBlendSelects the material’s additive/alpha blend behavior
SubUVWidth, SubUVHeightTexture-sheet dimensions; use 1, 1 for a single image
TexCoordSpeedTexture-coordinate movement; keep zero for a static frame
DistortAmountDistortion 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:

LayerColor textureOpacityStarting blend choice
Glowing shapesColored glyphsIndependent coverage maskAdditive
Dark silhouettesDark artwork with visible edgesWhite inside the shapeNon-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 typeOwns
worldEffect.lengthTotal authored timeline length, including the outro
trackRoot → effectTrackGroupCollection of tracks
effectTrack.itemsHandled timeline items
worldEffect.eventsReferences to the same item objects used by tracks
effectTrackItemParticlesA timed particle resource invocation
effectTrackItemLoopMarkerThe timeline’s loop-marker item
effectLoops[] → effectLoopDataLoop start/end times
effectTrackItemSoundA 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

ClockExampleSetting
Gameplay durationSix seconds of an optics statusTweakDB duration record
Presentation timelineIntro, repeating motion, outro.effect times and loop range
Particle lifetimeHow long one spawned frame survivesAuthored 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

  1. Create or open a mod-owned .effect with a worldEffect root and PC cooking target. Inspect the reference’s group, track, and event types.
  2. Add an effectTrack beneath the root group. Add a handled effectTrackItemParticles to its items array.
  3. Set particleSystem to the exact archived .particle depot path.
  4. Set timeBegin and timeDuration. These timeline values use seconds; do not copy millisecond values from a scene dialog event.
  5. Give the item a unique ruid. Add the matching object reference to the root events array rather than constructing another item with the same ID.
  6. Inspect alpha, emissionScale, size, and velocity. The constant-value setup uses effectEffectParameterEvaluatorFloat with a CEvaluatorFloatConst evaluator of 1.0 for each. A zero alpha or emission scale can hide otherwise valid particles.
  7. Set the root length to cover the item, save, and reopen it. Check the track, item reference, particle path, evaluators, and RUID.
  8. 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

  1. Put intro tracks before 0.2 seconds.
  2. Add a loop marker beginning at 0.2, lasting 0.8 seconds.
  3. Set the corresponding effectLoopData.startTime to 0.2 and endTime to 1.0. Keep the marker and loop table consistent.
  4. Place the motion tracks entirely within that interval.
  5. Put finite outro tracks at or after 1.0. Set the root length to the latest outro event’s end.
  6. 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:

FrameBeginDurationEnd
hold_000.20.10.3
hold_010.30.10.4
…………
hold_070.90.11.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

DomainExampleMust match
TweakDB VFX recordCQAEffects.FXThe status’s VFX array
Effect CNamecqa_optics_glitchStatusEffectFX.name and entEffectDesc.effectName
Component CNamecqa_optics_fxYour independently named spawner component
Effect resource pathmod\cqa\effects\optics_npc.effectDescriptor’s effect reference
Placement tagHeadA compatible slot on the target entity
Timeline event RUIDAn unsigned 64-bit valueThe 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

  1. Inspect QuickHack.BlindHack, Items.BlindProgram, and their inherited action effects in the installed TweakDB.
  2. Derive new item and action records under your own namespace.
  3. Create ObjectActionEffect records using ObjectActionReference.Target for the two custom statuses.
  4. In the derived action’s completionEffects, replace the original blindness payloads and deliberately handle any spread payload. Preserve unrelated upload and control effects.
  5. Point the derived item’s objectActions at your action. Give its UI fields matching localized keys.
  6. 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

  1. Reopen the cooked .effect, particles, and patch entities in WolvenKit.
  2. Compare the status’s VFX name with the descriptor’s effectName exactly.
  3. Check that the patch targets the entity template the character actually uses, and that the patch adds your independently named spawner.
  4. Match each particle and sound RUID to its compiled placement row.
  5. Check the loop range, finite outro, root length, and sound position.
  6. Check particle drawers, compiled simulation settings, materials, texture paths, and opacity masks.
  7. 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.

TestWhat to watch
Normal expiryStatus ends; outro finishes; no particles remain
Early status removal through your gameplay routeVisual leaves by the intended outro or immediate-stop policy
Reapply while activeNo uncontrolled layering, unexpected restart, or duplicated exit sound
Reapply after expiryA complete fresh sequence starts and ends once
Target dies or leaves streaming rangeNo floating image or sound remains without its owner
Change first/third-person viewSupported player templates resolve the correct presentation
Reload after expiryNo restored blindness, stranded overlay, or unexpected replay
Save during the active status, if permittedLoaded state behaves coherently; record whether the timer resumes or restarts
Enable another mod affecting the same characterThe 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.

SymptomInspect first
Item has no name or descriptionOnscreen keys, localization resource, ArchiveXL registration
Item exists but produces no statusItem action, completionEffects, recipient, status record IDs
Blindness works but V shows a white screenExact VFX/SFX casing, derived status, custom name, player spawner patch
V works but an NPC has no custom visualThe NPC’s real entity path, separate NPC patch, descriptor name
Vanilla NPC works but custom character does notExplicit patch target for the custom .ent and compatible slot bindings
Status starts but neither variant rendersEffect-name join, patch loading, descriptor path, event placement rows
Sound or particles appear at the wrong placePlacement tag, slot binding, relative transform, RUID-to-placement join
Image is much too large despite a size editCompiled particle size and screen/billboard drawer settings
Old frames remain visibleBaked lifetime, emission rate, burst count, emitter loops, overlapping intervals
Shapes are invisibleTrack alpha/emission scale, material opacity, imported alpha channel, texture paths
Dark shapes look like faint outlinesIndependent coverage mask, blend choice, material layer, background contrast
Pink looks red or excessively brightTexture color multiplied by tint and ColorMultiplier, then in-game exposure
Sound repeats on every motion cycleSound event placed inside the loop
Outro never appearsLoop-break route, loop boundaries, outro timing, root length, immediate-stop behavior
Effect persists after the statusParticle 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 behaviorPrimary guideSystems that must agree
Send a text, offer a reply, or start a callMessages, calls, and conversationsJournal, localization, phone state, and optionally scene/audio
Turn a choice into later contentBranching, choices, and debriefsChoice owner, facts, gates, journal, and downstream phases
Reach, leave, interact, hack, plant, or depositAreas, devices, and hackingWorld placement, NodeRefs, controller state, quest logic, and save identity
Acquire an item, read a shard/file, or scan a clueItems, shards, files, and scansTweakDB or journal content, localization, inventory/scan producer, and quest observer
Place an actor or expose a device interactionWorkspots and interactionsCommunity or entity template, world placement, workspot, controller, and quest lifecycle
Open, close, lock, or expose a world doorDoors are device contractsDoor entity/controller, NodeRef, supported action, interaction policy, navigation, and persistent state
Wait in real time, game time, or for a time periodDelays, facts, and persistenceClock domain, wait semantics, durable facts, reload, and re-entry policy
Meet or coordinate a named NPCNPC interaction and meetingsCommunity identity, readiness, acquisition, scene or interaction, and cleanup
Monitor stealth, run combat, plant, or destroyStealth, combat, and destructionParallel monitors, encounter owner, device/target capability, outcomes, and cleanup
Show a custom optics overlay or an NPC-attached visualCustom effectsStatus records, entity spawner, effect timeline, particles, materials, and textures
Release, escort, defend, carry, or place an NPCRescue, escort, defend, and carryActor ownership, AI roles, ordered volumes, combat/carry state, and terminal cleanup
Mount, ride in, drive, or steal a vehicleMount, ride, drive, and theftVehicle identity, seat roles, mount conditions, route volumes, and lifecycle policy
Deliver, remove, chase with, or race a vehicleVehicle delivery, cleanup, chase, and raceVehicle state, trigger/speed observations, AI route or race systems, restrictions, and cleanup
Resolve an outcome, debrief, reward, and finishRewards, switches, and outcomesDurable 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:

  1. define durable facts, journal states, outcomes, and one-shot policy;
  2. inventory every world, device, community, character, scene, item, and vehicle owner;
  3. prove placement, NodeRefs, streaming bounds, identity joins, and readiness;
  4. compose the smallest activity path and its authoritative completion signal;
  5. add optional monitors, branching, presentation, rewards, and cleanup;
  6. 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 experiencePrimary ownerQuestphase responsibilityAdditional assets
One-way textgameJournalPhoneMessage beneath a contact conversationActivate the message in the intended orderJournal contribution and onscreen localization
Text offer or reply menugameJournalPhoneChoiceGroup and gameJournalPhoneChoiceEntryActivate the group, observe the winning entry, and persist the resultSame journal and localization owners
Audio call statequestPhoneManagerNodeDefinitionStart or end a call for two journal contactsBoth contact paths; phone restrictions must be deliberate
Voiced holocall.scene launched by questSceneNodeDefinitionCoordinate the call and consume a named scene resultScene, actor/context bindings, subtitles, VO map, WEMs, and any lipsync resources
In-person conversation.scene plus a ready actor/communityAcquire the actor, start the scene, consume its named exit, then clean upCommunity, 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:

  1. a mod-owned .journal contribution containing the contact, conversation, messages, choice groups, and choice entries used by the route;
  2. globally unique onscreen localization keys for every contact name, thread title, message, and choice label;
  3. ArchiveXL registration for the journal and onscreen localization resources;
  4. a mod-owned .questphase with explained input, output, and cut sockets;
  5. separate facts for presentation sent, player response, and activity completion when those meanings differ;
  6. 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:

FieldRequired meaning
type.path.classNamegameJournalPhoneMessage
type.path.realPathFull path to exactly one message
type.path.fileEntryIndexIndex of the containing contact in the path; commonly 1, but calculate it
type.sendNotificationWhether this activation should request phone presentation
incoming socketActive
outgoing socketOut 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
StepNative node and decisive payload
1–2questJournalNodeDefinition → questJournalEntry_NodeType, entered through Active
3 and 5questFactsDBManagerNodeDefinition → questSetVar_NodeType, setExactValue: 1, value: 1
4questPauseConditionNodeDefinition → questJournalCondition → questJournalEntryVisited_ConditionType
6Normal 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
ConcernNative representation
Winning choicequestJournalEntryState_ConditionType, state: Succeeded, individual gameJournalPhoneChoiceEntry path
Branch responseOne questJournalNodeDefinition entered through Active
Durable outcomeOne branch-specific fact writer before convergence
ConvergenceTwo-input questLogicalXorNodeDefinition, with both inputs and Out1 inspected
Required acknowledgementquestJournalEntryVisited_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.questphase contains questPhoneManagerNodeDefinition operations whose nested questCallContact_NodeType uses caller, addressee, mode: Audio, and distinct StartCall or EndCall phases.
  • Observed in vanilla: base\quest\side_quests\sq011\phases\sq011_concert.questphase launches base\quest\side_quests\sq011\scenes\sq011_09_nancy_call.scene through a questSceneNodeDefinition. The retained scene node has a soft sceneFile, a tagged scnWorldMarker, named scene inputs/outputs, Holocall INT, Holocall RET, and Prefetch. A nearby questPhone_ConditionType records caller, addressee, and callPhase: 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
OwnerWhat must be authored and tested
Phone operationCorrect caller/addressee gameJournalContact paths, mode, phase, restriction, rejectability, avatar/visual settings
Scene nodeExact .scene depot path, location/context, input socket, every completion/rejection/interruption output
SceneActors, screenplay, sections, events, named exits, and cleanup
Localization/audioSpoken-line IDs, subtitle entries and map, VO map, WEMs, lipsync slot/resource contract
QuestWhat 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

  1. Build the journal tree and localization resource first. Reopen both and verify every full path and secondary key.
  2. Add the questphase journal nodes. Set concrete entry classes, fileEntryIndex, notification flags, and incoming state sockets.
  3. Wire the sequence by socket identity. Screen position is not execution order.
  4. Add one persistent sent guard and separate branch/completion facts. Avoid a single fact whose meaning changes halfway through the flow.
  5. For multiple choices, fan out to individual state waits, write the result on each branch, and converge only after the matching reply.
  6. For a required acknowledgement, wait on the final message’s visited state. Do not treat a journal node’s Out as acknowledgement.
  7. Save, reopen, serialize a focused review copy, and resolve every node, socket, condition handle, and journal path.
  8. 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:

  1. first install on an untouched save;
  2. save and reload before the opening message;
  3. save with the choice group active;
  4. one run for every choice from the same untouched baseline;
  5. save after the reply but before required final acknowledgement;
  6. completed-save reload and re-entry attempt;
  7. removal isolation from the original pre-install save;
  8. 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

SymptomInspect first
Contact or message never appearsJournal registration, full path, entry class, fileEntryIndex, localization registration
Message appears twiceSent guard timing, duplicate root entry, completed-save re-entry
Choice group appears but never advancesWait path points to the group instead of the individual choice, or wrong state dimension
Both replies appearBranch conditions, mutual-exclusion facts, and convergence wiring
Final message appears after completionCompletion used activation or a pacing delay instead of the intended visited gate
Call UI starts but no dialogue playsPhone operation succeeded while scene input, actor/context, subtitle, VO, or WEM ownership failed
Dialogue ends but phone remains busyMissing or unreachable EndCall, rejected/interrupted output not handled
Only one save behaves incorrectlySaved 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 decisionObserve it withPersist it withDo not substitute
Phone replyJournal condition on the individual gameJournalPhoneChoiceEntryquestSetVar_NodeType on the winning routeChoice-group Active state
Scene optionScene graph option/section routing, then a named questphase output or fact handoffFact writer immediately after the named resultChoice caption or embedded locstring ID
Current world/fact statequestConditionNodeDefinition for a snapshot, or a deliberate wait for later stateA result fact if later phases need the answerA Pause Condition when an immediate false route is required
First satisfied asynchronous routeParallel waits followed by a tested convergence nodeWinner fact before convergenceThe name “XOR” as proof that losing listeners are cancelled
N-way state selectionquestSwitchNodeDefinition with case-bound conditionsCase-specific fact or phaseA 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
NodeDecisive properties
Each waitquestPauseConditionNodeDefinition → questFactsDBCondition → questVarComparison_ConditionType
ComparisoncomparisonType: Greater, branch-specific factName, value: 0
Each writequestFactsDBManagerNodeDefinition → questSetVar_NodeType, branch-specific fact, setExactValue: 1, value: 1
ConvergencequestLogicalXorNodeDefinition, 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
PropertyExact retained value or boundary
Positive routequestVarComparison_ConditionType, Greater, value 0
Zero routequestVarComparison_ConditionType, Equal, value 0
Result writesSeparate exact-value fact setters
ConvergenceTwo-input, one-output logical XOR
PresentationThe 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 partRequired relationship
behaviourThe 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
OtherwiseExplicit additional output; connect it if missing state must not stall silently
InThe 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:

  1. make conditions exclusive whenever the narrative promises one outcome;
  2. give each case a durable result write before branch-specific side effects;
  3. connect Otherwise to a logged/recoverable fallback or explicit termination;
  4. do not mutate conditions inside one case and expect undocumented fall-through;
  5. 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:

  1. route the option inside the .scene;
  2. end each meaningful route through a named scene output, or write a scene-owned fact at a deliberate handoff;
  3. expose matching sockets on the questSceneNodeDefinition;
  4. write the durable quest outcome immediately after the named output;
  5. 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

  1. Draw the player-facing alternatives and mark the system that owns each choice.
  2. Allocate source-state and durable-outcome facts separately.
  3. Add the smallest native condition or switch shape that represents the required timing.
  4. Set every comparison, path, socketId, and socket count before connecting edges.
  5. Write each outcome before any later phase can consume it.
  6. Complete branch-owned journal, inventory, scene, device, or world effects.
  7. Connect the convergence and exactly-once shared continuation.
  8. Save, reopen, and inspect resolved source/destination sockets from both endpoints.
  9. 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:

CaseRequired observation
Outcome AOnly A state/effects occur; shared continuation occurs once
Outcome BOnly B state/effects occur; shared continuation occurs once
Every additional switch caseCorrect case socket, durable fact, and branch assets
No case trueOtherwise or documented fallback occurs
Multiple cases trueDeliberately specified result; no accidental duplicate continuation
Save before choiceReload still presents a valid choice once
Save after source choice, before durable writeNo lost or duplicated outcome
Save after durable write, before convergenceCorrect branch resumes and shared effects occur once
Completed-save re-entryNo branch side effect or reward repeats
Replay from untouched saveFirst-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

SymptomInspect first
Wrong phone branchIndividual choice path, Succeeded state, and source fact write
Gate never leaves one branchFact name, comparison and value, whether the wait was started
Both branch effects occurMutual-exclusion invariant, stale save facts, losing listener, duplicate edges
Switch selects an unexpected caseCondition order, socketId to Case<id> mapping, multiple-true inputs
Switch stallsMissing true condition and unconnected Otherwise
Optional result is never recordedNegative/out-of-domain value, boundary never entered, or wrong result fact
Shared continuation runs twiceConvergence semantics, late listener, multiple incoming signals, absent one-shot guard
Correct choice but wrong debriefOutcome 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.

ConcernRequired ownerReview evidence
World namespaceQuest-prefab root plus a Quest streaming descriptorLocal root in phasePrefabs; matching full root in the descriptor
AreaworldTriggerAreaNode, AreaShapeOutline.buffer, notifier, and nodeDataFull child NodeRef, transform, bounds, decoded points, height, and questTriggerNotifier_Quest
Objective presentationJournal objective, description, localization, and optional journal mappinFull typed paths, correct fileEntryIndex, and registered localization
World markerUsually a dedicated worldStaticMarkerNode in an AlwaysLoaded sectorFull marker NodeRef and world-space navigation endpoint
DeviceworldDeviceNode or evidence-matched entity placementEntity template, appearance, node-local instanceData, controller, components, slots/workspots, and full NodeRef
Quest-side device accessPrefab dependency plus action and/or conditionExact controller class, action CName, condition function CName, and local target NodeRef
Persisted lookupOnly the evidence-required .devices or .psrep contributionExact NodeRef hash, controller class, merge result, and reload behavior
Plant itemTweakDB item record and inventory ownershipItem ID, grant/pickup owner, quantity, consumption rule, and save behavior
Drop pointExisting live kiosk plus its controller contractExact 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:

OwnerPropertyMeaning in the inspected shape
questPauseConditionNodeDefinitionconditionHandle to the condition wrapper
questTriggerConditiontriggerAreaRefLocal NodeRef beneath an available quest-prefab root
questTriggerConditionactivatorRefEmpty entity reference in the inspected player form
questTriggerConditionisPlayerActivator1 in the inspected player form
questTriggerConditiontypeEntered, 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:

ConditionUse when the story meansReload boundary
EnteredCross from outside to inside after activationLoading already inside may not create a new edge
ExitedCross from inside to outside after activationStarting outside is not a leave event
IsInsideProceed whenever the player is currently insideAlready-inside activation and reload require a retained test
IsOutsideProceed whenever the player is currently outsideMust 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
BoundaryOwnerWhat it may establish
Streaming/setupQuest sector, community area, and broad trigger after readinessThe world and contact have time to become available before scene start
ReadinessSpawn Manager action plus CharacterSpawned conditionThe named community entry exists before scene acquisition
EngagementNarrow trigger or interaction owned by the running sceneThe player deliberately reaches the presentation point after scene setup
Scene originscnWorldMarker and scene start nodeScene transform and actor acquisition context
CleanupNamed scene exit, later delay/area, and community deactivationA 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 payloadDecisive properties
questInteractiveObjectManagerNodeDefinitiontype: questDeviceManager_NodeType
questDeviceManager_NodeTypeParamsdeviceAction, deviceControllerClass, objectRef, actionProperties, slotName
questPauseConditionNodeDefinitioncondition: questObjectCondition
questDevice_ConditionTypedeviceConditionFunction, 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:

OrderNative nodeRequired binding
1questJournalNodeDefinitionObjective Active
2questInteractiveObjectManagerNodeDefinitionDevice, controller class, action
3questPauseConditionNodeDefinitionSame device/controller plus completion function
4questItemManagerNodeDefinitionLocal-player item removal
5questJournalNodeDefinitionObjective Succeeded
6questFactsDBManagerNodeDefinitionExact 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 propertyRetained value or binding
managerNameDropPointManager
componentNamecontroller
PSClassNameDropPointControllerPS
objectRefExact native drop-point entity reference
eventReserveItemToThisDropPoint 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:

  1. 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.
  2. Add the Quest descriptor and mod-owned sector resources. Declare one quest-prefab root and register every full child NodeRef under it.
  3. For an area, create the trigger node, placement, notifier, and authoritative outline. Reopen and decode the outline after saving.
  4. 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.
  5. 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.
  6. 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.
  7. Add explicit success, marker cleanup, inventory mutation, fact, and community cleanup edges. Do not treat an activation node’s Out as proof that the player completed the activity.
  8. Save, reopen, serialize, and inspect the concrete node classes, non-null handles, socket connections, NodeRefs, item IDs, and controller CName values.
  9. 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.

CaseWhat it distinguishes
Start outside the areaPremature state-condition or objective activation
Cross the boundary normallyTrigger geometry, notifier, activator, and graph order
Load before crossingRestoration before an edge or state change
Load already inside while the wait is activeEntered versus IsInside behavior for this exact graph
Leave through two directions and elevationsOutline, height, bounds, and real site geometry
Approach the device before and after its sector streamsVisibility versus controller and interaction readiness
Cancel or disconnect mid-hackController recovery and objective non-completion
Complete once, then reloadDuplicate action, reward, item mutation, and marker prevention
Stream away and returnDevice, marker, and community reacquisition
Reinstall against an old saveSave-backed fact, journal, trigger, and persistent-device contamination
Remove the mod and load a disposable copyCleanup/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

SymptomInspect first
Objective appears but area never completesTrigger notifier, local/full NodeRef chain, activator fields, condition type, outline buffer, and player Z
Leave completes immediatelyGraph order and an already-true IsOutside condition
Meeting starts before the contact existsCommunity activation, CharacterSpawned, broad setup order, and scene acquisition
Marker routes to the wrong placeDedicated marker transform and navigation endpoint; do not assume the trigger/device root is walkable
Device renders but has no expected promptEntity template, interaction component, controller binding, workspot/slot, instance-package CRUIDs, and initial state
Quest cannot find a visible devicePrefab dependency, local/full NodeRef context, controller class, and evidence-required .devices registration
Hack completes before player inputA quest action was sent before a player-driven condition wait
Plant succeeds but item remainsItem TweakDBID, local-player reference, remove mode, quantity, and branch actually reached
Drop point has no deposit choiceReservation event, exact item ownership, native kiosk reference, controller class, and deposit fact
Old files, prompts, or device state returnSave-backed device identity; use a fresh NodeRef or a pre-stream clean save
A packed candidate loads but behaves incorrectlyTreat 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

ConcernOwnerRequired decision
Inventory identityTweakDB item recordExact TweakDBID, quantity, stack behavior, tags, and whether a readable action exists
Acquisition producerWorld loot, device, scene, or quest item-manager nodeWho actually adds the item and whether a notification is expected
Acquisition observerquestInventory_ConditionType or an explicit factQuantity comparison, player/object selection, and already-owned behavior
Readable contentMod-owned journal treeLeaf type, full path, localization, registration, and containing-group fileEntryIndex
Shard bridgeItem’s secondary Read actionExact journal entry target and presentation behavior
Computer contentEntity template plus bound instance RedPackageController chunks, component CRUIDs, non-empty file/email structures, menus, and per-element fact
Scan targetStreamed entity/deviceFull/local NodeRef, scanner component, highlight owner, placement, and streaming range
Scan observerquestScan_ConditionTypeEvent type, object reference, activation order, and whether every clue is required
Objective presentationJournal objective, description, optional clue entries, and mappinsExplicit Active/Succeeded/Inactive edges and registered localization
PersistenceSave plus world/device/journal stateClean 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 propertyRetained grant value
entityRefLocal-player questUniversalRef
itemIDBound item TweakDBID
nodeTypeAddItem
quantityBound positive quantity
sendNotification1
flagItemAddedCallbackAsSilent0

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 questionWhy it must be explicit
Remove one or every matching itemRemoveAll is not a quantity-one promise
Show a notificationSilent callback and sendNotification are separate fields
Consume before or after successGraph order determines whether a failed interaction loses the item
Retry after reloadInventory and completion facts may restore from different save-backed owners
Deliver to a kioskThe 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 contractSuitable observed signalEvidence boundary
Player acquired the readableInventory ownership while it remains stackedStructurally validated reduced inventory wait; reload and consumption still need testing
The award step occurredDedicated acquisition fact set by the known producerRuntime-proven only for the exact 82C221... route
Player opened a full Journal readerA matching visited condition, if the exact UI path emits itExperimental for a generic inventory shard
Player opened the pickup-notification previewNo generic visited signal is establishedExperimental; 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:

GroupLeafPrincipal player-facing fields
gameJournalOnscreenGroupgameJournalOnscreentitle.value, description.value, tag, iconID
gameJournalFileGroupgameJournalFiletitle.value, content.value, pictureTweak, videoResource
gameJournalEmailGroupgameJournalEmailsender.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:

OrderNodeBinding
1Optional questJournalNodeDefinitiongameJournalFile path Active
2questJournalNodeDefinitionObjective Active
3questPauseConditionNodeDefinitionDedicated document fact greater than zero
4questJournalNodeDefinitionObjective 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

  1. Create or clone the mod-owned journal branch first. Add the precise gameJournalOnscreen, gameJournalFile, or gameJournalEmail leaf and its localization, then register both resources through ArchiveXL.
  2. 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.
  3. In the questphase, add explicit objective activation before the inventory, fact, or scan pause condition. Add a separate Succeeded state after it.
  4. For a quest grant or removal, add a questItemManagerNodeDefinition and inspect every parameter rather than copying a whole node blindly.
  5. For each scan, bind questScan_ConditionType.objectRef to the exact local target NodeRef made available by the phase’s prefab ownership. Confirm the corresponding full world registration and concrete target.
  6. For a terminal, inspect the exact .ent component IDs and author the smallest node-local package with non-empty content. Bind the device element’s journalPath and questInfo.factName to mod-owned values.
  7. Add marker cleanup, clue journal activation, fact writes, item grants, and presentation delay only on explicit post-condition edges.
  8. Save and reopen every CR2W. Serialize it and review concrete nested types, handle references, item IDs, journal class names, fileEntryIndex, scan eventType, target refs, and graph sockets.
  9. 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

CaseWhat it distinguishes
Start with zero itemsNegative control for an inventory wait
Start already owning the required quantityIntended state semantics versus a new-acquisition requirement
Acquire from the real world sourceProducer, notification, inventory update, and quest wait
Save while waiting, then acquire after reloadCondition restoration and duplicate-grant prevention
Open the pickup preview and the full Journal separatelyPreview behavior versus visited state
Consume the readable itemWhether the chosen completion signal survives item removal
Open the terminal before its objectivePre-set fact and save-backed device/journal behavior
Open the terminal during the objectiveExact content element, fact, and single completion
Scan clues in intended orderMarker handoff, target identity, Finished, and outputs
Attempt a later clue earlyOrdered topology and inactive-target presentation
Stream away and return mid-investigationTarget, marker, condition, and highlight reacquisition
Reload after completionDuplicate item, journal entry, fact, objective, and marker prevention
Reuse an old device save after changing contentPersistent 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

SymptomInspect first
Item objective completes immediatelyAlready-owned quantity, comparison type, and whether acquisition rather than ownership was intended
Grant node runs but no expected item appearsItem record existence, exact TweakDBID, local-player reference, quantity, and TweakXL load
Plant or handoff leaves the itemRemove mode, item ID, branch route, and whether the device/drop point owns removal instead
Shard appears but the quest stalls after opening itInventory consumption, preview versus full-reader state, and the actual acquisition/read signal
Journal entry is blankLeaf type, full path, containing-group fileEntryIndex, ArchiveXL journal registration, and localization
Computer has no Files tabEmpty filesStructure, wrong controller chunk, component CRUID mismatch, content assignment, or stale saved device state
File opens but fact stays zerogamedeviceDataElement.questInfo.factName, bound controller, or scene/UI output owner
Scan never completesTarget NodeRef, streaming, scanner component, eventType, object-reference form, and whether the condition was active
Scan completes on the wrong objectLocal/full identity collision, prefab root, or copied target reference
Highlight remains after scanner exitDetermine whether the template or quest owns it; remove the custom layer before adding more cleanup events
Later clue appears too soonGraph 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 designPrimary asset ownerQuest-side responsibilityCurrent status
Stationary contact waits at a poseCommunity plus one worldAISpotNode and .workspotActivate, wait for spawn, acquire in scene, then clean upTest pose, acquisition, and cleanup at the selected location
Guard or civilian follows a finite spot sequenceCommunity time period plus multiple AI spots and compatible AI behaviorActivate, observe milestones if needed, stop/clean upShapes are Structurally validated; general movement behavior is Experimental
Player uses, plugs into, or hacks a deviceDevice .ent, instance package, controller, interaction/workspot/slotEnable only if required, wait on a controller function or factTest the selected controller, action, and interaction
Quest waits until an actor is in a workspotCharacter condition targeting the actorActivate the wait only after the producing AI/workspot routeCondition family is Observed in vanilla; test completion and reload
Scene needs an actor at a presentation pointCommunity/AI preparation plus scene marker and actor acquisitionWait for readiness, start scene, route named exitTest 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[]
PropertyOwner and meaning in the inspected shape
characterRecordIdCommunity entry; chooses the character TweakDB record, not the world placement
entryNameCommunity-local identity used by activation, conditions, and scene acquisition
phaseNameCommunity-local mode selected by initial state or Spawn Manager operations
spotNodeRefsFull or context-valid references to placed AI spots
isSequencePeriod-level sequence flag in the retained shape; not a complete movement recipe
quantityRequested 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 valueCompiled-area value
communitySpawnEntry.entryNamecommunityCommunityEntrySpotsData.entryName
communitySpawnPhase.phaseNamecommunityCommunityEntryPhaseSpotsData.entryPhaseName
communityPhaseTimePeriod.hourcommunityCommunityEntryPhaseTimePeriodData.periodName
communityPhaseTimePeriod.isSequencecommunityCommunityEntryPhaseTimePeriodData.isSequence
communityPhaseTimePeriod.spotNodeRefscommunityCommunityEntryPhaseTimePeriodData.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.

ConcernCorrect owner
Actor selection and appearanceCommunity entry and character/entity/appearance chain
Activity or animation setupReferenced .workspot resource
Position and facingAI spot nodeData transform
Quest-prefab identityFull/local NodeRef chain and placement hash
Continuing versus finite useisWorkspotInfinite plus the community-period design
Ordered routeMultiple unique spots, mirrored order, isSequence, compatible AI, and navigable placement
ReadinessSpawn 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:

BoundaryRequired decision
Door assetWhich .ent, instance data, components, controller class, and persistent identity own the door?
Quest commandIs the quest supposed to force a state, or must the player use an asset-owned prompt?
CompletionWhich exact controller function, fact, trigger, or later event proves the promised result?
TraversalWhat happens to collision, navigation, companions, combatants, and the player if the action fails or the door closes?
LifecycleShould 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

  1. 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.
  2. Select one .workspot path from an evidence-matched vanilla comparison. Inspect it, but reference the depot asset rather than extracting and redistributing it.
  3. Add a worldAISpotNode to the Quest sector. Set spot to an AIActionSpot, bind its resource, and choose finite/infinite and static fields deliberately.
  4. Add the matching nodeData placement and full child NodeRef. Confirm the transform leaves clearance and the facing matches the intended player approach.
  5. Add the spot to the sector’s nodeRefs and to the compact registry’s persistent spot data with the same world-global identity.
  6. Bind the registry entry’s spotNodeRefs, then mirror entry, phase, period, order, and spot IDs in the compiled community area.
  7. Keep the entry inactive until the quest requests it unless a known-good comparison requires another lifecycle. Add a CharacterSpawned readiness wait before a scene or interaction tries to acquire the actor.
  8. Save and reopen every CR2W. Serialize and inspect all handles, NodeRefs, global IDs, character records, appearance names, period names, and workspot resource references.
  9. Pack and extract the project, then compare payloads and ArchiveXL registrations before entering the game.

Author a device interaction in WolvenKit

  1. Inspect the exact device .ent: controller, scanner, interaction components, component IDs, workspots, and relevant slots.
  2. Place a mod-owned worldDeviceNode or evidence-matched entity node with a stable full NodeRef and a transform that leaves the interaction path clear.
  3. Add the smallest node-local instance package needed for content or state. Preserve template-matched component identities; do not paste an unrelated RedPackage.
  4. Add a sparse .devices contribution only when controller lookup evidence requires it. Add .psrep only when a persistence test establishes that requirement.
  5. 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.
  6. Keep a separate marker at a verified HUD/navigation transform if the entity root or interaction slot is not a useful route endpoint.
  7. 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

CaseWhat it distinguishes
Approach from outside streaming rangeSector, community area, spot, and device availability
Activate the community onceEntry/phase/period selection and intended spot acquisition
Load before activationClean negative control for active-on-start leakage
Load after activation but before interactionSaved community/workspot or device state
Approach from several directionsClearance, facing, navmesh, collision, and prompt range
Interrupt before completionActor/device recovery and objective non-completion
Complete normallyProducer signal, quest wait, presentation, and cleanup order
Enter combat during the activityAI/workspot interruption policy; only include if the design supports combat
Stream away and returnReacquisition without duplicate actor, stuck spot, or missing prompt
Reload after the named scene/device exitPersistent owner release and duplicate prevention
Replay from untouched clean saveOne-shot state and deterministic placement
Reuse an old device identity after changing its packageDemonstrates 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

SymptomInspect first
Actor spawns at the wrong spotRegistry spotNodeRefs, area spotNodeIds, full NodeRef, global ID, and sector placement
Actor clips, floats, or faces awayAI spot transform, workspot assumptions, ground contact, clearance, and navmesh
Actor activates but never settlesWorkspot resource, finite/infinite fields, phase/period, AI role, and navigation
Sequence stalls or skipsUnique spot identities, mirrored order, isSequence, compatible AI, and interruption state
Scene cannot acquire the actorCommunity and entry names, readiness wait, active phase, streaming, and scene actor reference
Actor disappears during the sceneCleanup edge, scene named exit, AI/workspot ownership, and deactivation timing
Device is visible but prompt is absentExact .ent, interaction component, slot/workspot, controller binding, instance data, and initial state
Prompt appears from the wrong sideEntity transform, slot/workspot local transform, collision, and approach space
Quest waits forever after interactionController function/fact producer, NodeRef context, and whether a quest command replaced player input
Removed interaction content returnsSave-backed persistent device identity; use a new NodeRef or a pre-stream save
Workspot condition never changesProducing 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:

OwnerRequired material
QuestRegistered root, child phase, objective, description if used, map pin, one-shot state, and every intended scene outcome
WorldRoot-owned quest prefab, community registry node, compiled community area, AI spot, setup and cleanup triggers, scene marker, and map marker
CommunityOne entry/phase join, character record, appearance, quantity, activation policy, and workspot mapping
ScenescnSceneResource, community-acquired actor, public entry, named exits, screenplay, events, and localization/audio dependencies
LifecycleInterruption 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;
  • CharacterSpawned does 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:

ScopeEntry/phase valuesAppropriate use
Whole communityNone / NoneThe quest owns every configured entry and intends to bring the complete community online
Named phaseExact entry and phase CName valuesOne 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 valueWhy it is wrong
Registry node’s numeric world identityPlacement identity is not the community NodeRef used by the quest condition
Compiled area’s sourceObjectIdIt joins compiled area to template; it is not a text NodeRef
AI spot global IDIt identifies the placed spot, not the community source
Entry name aloneIt narrows an entity reference only when paired with the correct community reference
Character TweakDBIDIt 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:

FieldMust agree with
communityParams.referenceSpawn action, readiness condition, prefab scope, and mounted community source
communityParams.entryNameRegistry template entry and compiled-area entry mirror
actorIdScreenplay sections, events, performer mappings, and actor-facing scene nodes
actorNameScene 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:

PropertyContract
sceneFileSoft depot path to the packed .scene resource
sceneLocationscnWorldMarker whose nodeRef resolves beneath the active prefab scope
Input socketExact public scene entry, such as start
Output socketsExact public scene End names, such as contact_done, accepted, or refused
interruptionOperationsExplicit interruption policy; an empty array is a bounded design choice, not a default guarantee
CutDestinationCut 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:

  1. Mount the registry, compiled area, AI spot, triggers, and markers under one verified quest-prefab scope.
  2. Confirm the registry entry and compiled-area mirror use the same entry and phase names, and that their world identity joins are correct.
  3. Set the contact inactive on start unless the surrounding quest deliberately owns an earlier activation.
  4. In the child questphase, add objective and mappin activation before the Spawn Manager action.
  5. Add the named or whole-community Activate payload.
  6. Add actor-specific readiness waits. Use an explicit all-of join when more than one performer is mandatory.
  7. Add the broad player IsInside setup wait. Add a checkpoint only when the save/retry policy has been designed and tested.
  8. Add questSceneNodeDefinition, its soft scene path, world marker, public entry, every named success/refusal output, and interruption outputs.
  9. Route each named output through its own durable fact/journal writes before convergence.
  10. Inactivate the meeting pin, activate any leave objective, and wait for the player to be outside a generous cleanup area.
  11. Deactivate the community, or explicitly transfer ownership to the next escort, combat, or scene phase. Do not do both.
  12. 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.

CaseRequired observation
Slow ordinary approachOne activation, one actor, one scene start, correct named return
Fast arrivalSetup wait does not outrun actor or scene-marker streaming
Stream away before setupReturn restores one actor and one armed meeting route
Save after activation, before readinessReload reaches readiness without duplication or deadlock
Save after readiness, before sceneReload either resumes the documented pre-scene state or is explicitly unsupported
Every named scene exitExactly one durable branch result and correct journal/mappin state
Leave during approachMeeting remains recoverable or follows an authored cancellation result
Combat or police intrusionActor behavior, scene interruption, and cleanup follow the stated policy
Post-scene cleanupActor remains until the scene is released and V crosses the cleanup boundary
Completed-save reloadMeeting, marker, scene, and community do not reactivate
Clean replayThe same installed build completes from a separate untouched save
Removal isolationRemoving 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

SymptomInspect first
Actor never appearsRegistry active-on-start policy, activation scope, entry/phase spelling, area/template identity join, AI spot, sector bounds
Spawn action exits but wait never resolvesobjectRef.reference, entry narrowing, comparison scope, stream coverage
Actor is visible but scene stallsScene communityParams.reference and entryName, performer mapping, scene marker, public entry
Scene runs but quest does not continueScene End name versus questphase output socket and connected edge
Actor disappears during dialoguePremature Deactivate, competing phase change, stream bounds, or another owner clearing the actor
Actor walks back after dialogueFollower/gameplay role cleared before the next lifecycle owner takes over
Reload uses an older routeSave history, active questphase/checkpoint, scene state, installed archive filename, and duplicate archives
New archive crashes at scene launchScene 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:

SystemRequired authoring
CommunityRegistry, compiled area, named entries/phases, AI spots, character records, appearances, factions/attitudes, and activation policy
WorldQuest prefab, combat-safe navigation, activation and cleanup volumes, device or destructible placement, bounds, and NodeRefs
QuestRequired and optional objectives, outcome facts, branch policy, reward/completion owner, and one-shot guard
Device/targetController class, supported action and condition function, persistent-state identity, interaction workspot, item record if consumed, and actual destruction capability if damage is required
LifecycleSpawn 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:

RoleNode/payloadDecisive properties
Start presentationquestJournalNodeDefinitionObjective path through gameJournalQuestObjective; enter through Active
Failure listenerquestPauseConditionNodeDefinition → questFactsDBCondition → questVarComparison_ConditionTypefailure_fact Greater 0
Stop listenerSame typesstop_fact Greater 0
Failure writequestJournalNodeDefinitionSame objective; enter through Failed
Quiet writequestJournalNodeDefinitionSame objective; enter through Succeeded
Durable quiet resultquestFactsDBManagerNodeDefinition → questSetVar_NodeTypesuccess_fact, setExactValue: 1, value: 1
ConvergencequestLogicalXorNodeDefinitionTwo 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:

  1. Treat one outcome fact as authoritative.
  2. Gate every journal writer so only an unresolved monitor may write.
  3. Make late signals bypass all state mutation.
  4. If explicit cut topology is required, connect and test the exact CutSource/CutDestination targets rather than assuming a phase exit cuts them.
  5. 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:

QuestionRequired 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:

PropertyRetained valueBoundary
WrapperquestCharacterConditionCharacter-condition schedule
Concrete typequestCharacterKilled_ConditionTypeName does not imply lethal-only behavior
objectRef.referenceEncounter community NodeRefNo entry names in the whole-community shape
comparisonParams.comparisonTypeGreaterOrEqualCoupled to the exact condition implementation
comparisonParams.count0Do not reinterpret as generic arithmetic outside the source-matched shape
comparisonParams.entireCommunity1Community-scoped result
killed / defeated / unconscious1 / 1 / 1Accepts 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:

Outcomekilleddefeatedunconscious
Lethal100
Spared/nonlethal011

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:

RoleOwner or binding
Expose the initiating promptDevice template, instance/controller package, interaction record, slot/workspot, and current persistent state
Observe connectionquestPauseConditionNodeDefinition carrying questObjectCondition / questDevice_ConditionType, the exact device NodeRef and controller class, and IsPersonalLinkConnected
End the interactionquestDeviceManager_NodeTypeParams bound to the same device/controller and QuestForceDisconnectPersonalLink, after the wait and presentation
Consume itemquestAddRemoveItem_NodeTypeParams, exact item TweakDBID, player reference, quantity/removal policy, and notification policy
Persist resultDedicated 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 threshold for 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

  1. Build and inspect the community, security/device, and target resources before adding completion gates.
  2. Author activation and cleanup areas with correct notifier and outline buffers.
  3. Add objective and mappin activation, then community activation.
  4. Wait for every actor whose readiness matters; do not infer a fixed roster from aggregate > 0.
  5. Add one explicit hostility command per intended actor and target.
  6. Add the exact lethal/nonlethal or protected-target resolution policy.
  7. If stealth is optional, split the failure and stop listeners from the same start edge and make late signals harmless.
  8. 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.
  9. If destruction is required, verify the target’s destructible behavior independently before trusting the quest condition.
  10. 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.
  11. 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

CaseRequired observation
Quiet stealth routeOptional objective succeeds once; required activity remains completable
Detection routeCorrect producer fails stealth once; required activity still follows its authored policy
Late failure after stopNo second journal write or contradictory fact
Save with monitor armedReload preserves one valid outcome route without duplicate listeners
Encounter approachNo actors before activation; every required actor spawns once on safe navigation
Lethal completionAccepted lethal states complete exactly once
Nonlethal completionAccepted incapacitated states complete exactly once; rejected states do not
Partial rosterOne unresolved or unstreamed entry cannot falsely complete the encounter
Wave transitionPrevious wave ownership ends before the next wave becomes authoritative
Plant cancel/retryCancel does not consume the item or write completion; retry can succeed
Destruction thresholdsBelow/at/above boundary behave exactly as the design relies on
Stream away/returnActors, device, target, and armed waits restore without duplicates
Completed reloadNo objective, hostility pulse, reward, or cleanup repeats
Clean replayBoth 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

SymptomInspect first
Optional stealth resolves immediatelyStop/failure facts inherited from the save, entry-time test used instead of a monitor, or wrong comparator
Both stealth outcomes writeLosing listener still active, no authoritative outcome fact, or simultaneous signal policy untested
Guards spawn but remain passiveMissing explicit target, wrong named entry, faction/attitude mismatch, threat command before readiness
Encounter completes earlyAggregate readiness/resolution scope, future/inactive entries, count/comparator payload, stale facts
Encounter never completes nonlethallydefeated/unconscious flags, actor identity, protected/invulnerable state, or wrong target roster
Second wave never appearsPrevious community not cleaned/transferred, next sector not streamed, reused phase state, or shared comparison counting the wrong entries
Plant objective completes on approachProximity used as completion instead of device condition
Plant action works only once across buildsSave-backed device state or reused NodeRef/controller persistence
Damage never advances objectiveTarget is not destructible, wrong object NodeRef, wrong threshold semantics, or damage source not supported
Actor disappears during cleanupDeactivation 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:

OwnerRequired material
Target communityRegistry, compiled area, named target entry/phase, character record, appearance, AI spot, activation/readiness policy, and long enough streaming bounds
Release devicePlaced device, controller class, exact action and completion function, device registry entry, interaction/workspot, and fresh persistent identity for materially changed tests
RouteWalkable navigation, three or more deliberate destination volumes, route/final markers, bounds, and recovery policy
Defend encounterProtected-target identity, attacker community/waves, hostility targets, survival or completion producer, failure policy, and cleanup
Carry/trunkCarry-capable target state, destination volume, vehicle identity if used, trunk interaction owner, and post-placement cleanup
QuestObjectives, 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:

RoleNative shapeRequired identity
Send actionquestInteractiveObjectManagerNodeDefinition → questDeviceManager_NodeTypeobjectRef, deviceControllerClass, deviceAction, and action properties all match the placed device
Confirm actionquestPauseConditionNodeDefinition → questObjectCondition → questDevice_ConditionTypeSame device NodeRef/controller plus exact deviceConditionFunction and parameters
Confirm targetquestPauseConditionNodeDefinition → questCharacterCondition → questCharacterSpawned_ConditionTypeCommunity NodeRef plus exact target entry name, entireCommunity: 0, and the intended comparison
Persist releasequestFactsDBManagerNodeDefinition → questSetVar_NodeTypeQuest-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:

  1. target begins in the intended released state;
  2. gameplay AI and follower role apply once;
  3. target follows through ordinary walkable space;
  4. target responds to combat as intended;
  5. stream-away/return and save/load preserve one valid role;
  6. 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 activityFinal-gate action
Quest ends and actor should leaveClear follower role, move to a safe community phase if needed, then delay deactivation
Defend targetRetain follower/gameplay ownership through the hold; clear only after success or terminal failure cleanup
SceneMove to the required scene/workspot state, wait readiness, let the scene acquire, and clear only after its named exit
Continue escortPreserve 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:

  1. failure before and after attackers engage;
  2. manual reload of the pre-defense checkpoint;
  3. automatic retry behavior if the design relies on it;
  4. clean restoration of target health/state and follower role;
  5. no duplicated attackers, timers, facts, notifications, or rewards;
  6. repeated failure followed by success;
  7. 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:

  1. the same target-still-mounted condition; and
  2. a player IsInside condition 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

  1. Build the target community and release device. Prove activation, named spawn, action, and completion function independently.
  2. Add release objective/action/condition/readiness/fact in one ordered child.
  3. Set gameplay AI for the same named entity, then assign the intended follower role.
  4. Place route volumes on verified navigation, give each current gate adequate streaming coverage, and add NPC-activator IsInside waits in order.
  5. Activate and clear route mappins around their matching waits.
  6. At the final gate, transfer the follower role to the defend, scene, carry, or cleanup owner instead of clearing it by habit.
  7. For defense, activate and wait for attackers before arming the timer or completion producer; add a separate named target-loss listener.
  8. Persist success/failure before community or role cleanup. Keep reward and terminal quest completion on a one-shot route.
  9. For carry, wait target-on-player first, then wait the logical AND of target-still-mounted and player-inside-destination.
  10. For trunk or workspot completion, use the focused condition only after the interaction/movement owner has been authored and tested.
  11. 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.

CaseRequired observation
Before activationTarget and attackers follow their authored inactive policy
Release cancel/failureNo released fact, no premature objective success, retry remains possible
Release successDevice state, named readiness, objective, and fact advance once
Escort ordinary routeTarget—not V—crosses every gate in order; pins follow the current gate
Player outruns targetLater gate cannot complete early; target recovers or follows documented failure policy
Combat during escortFollower role, target survival, and return to route are correct
Save between every gateReload restores one target, one current pin, and the correct next gate
Defend successAttackers spawn before timing, success wins once, survivors and role clean up safely
Defend failureTarget outcome, journal state, failure fact, attacker cleanup, and retry/terminal policy are exact
Near-simultaneous defend signalsOnly the intended result mutates state; late losing signal is harmless
Carry pickup/drop/re-pickupMount condition follows the actual target state and cannot complete from history alone
Carry destinationTarget remains mounted while V is inside; completion and cleanup occur once
Trunk placement/removalExact object/vehicle pair toggles the observed state; wrong vehicle cannot satisfy it
Workspot arrival/exitCondition follows the intended actor and spot; interruption does not deadlock cleanup
Stream away/returnTarget, current role, route gate, attackers/device, and objective restore without duplicates
Completed reloadNo release action, follower assignment, wave, timer, reward, marker, or cleanup replays
Removal isolationRemoving 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

SymptomInspect first
Release action fires but actor stays restrainedController action/function pair, same device NodeRef, target community phase, readiness identity
Actor appears but does not followGameplay AI tier, assign-role params, target community/entry, follower ref, navmesh
Gate advances when V arrives aloneisPlayerActivator, empty/named activatorRef, wrong trigger consumer
First gate never advancesVolume vertical placement, notifier, target identity, streaming bounds, target on valid navigation
Actor walks back at final gateFollower role cleared before the next owner, persistent community AI returning to its spot
Defense timer completes before combat beginsTimer armed before attacker readiness and threat injection
Defense succeeds and then failsLosing listener still active, no authoritative outcome, untested XOR/cut semantics
Retry duplicates attackers or rewardsCheckpoint boundary, persistent facts/journal, active listeners, cleanup, one-shot guard
Carry completion survives dropping the targetHistorical mount event used without a current mount-and-destination AND gate
Trunk condition accepts wrong vehicleanyVehicle, playerVehicle, or vehicle entity reference too broad
Workspot wait never resolvesMovement owner never assigned the spot, wrong spot NodeRef, incompatible actor/workspot, interrupted AI role
Cleanup visibly pops the targetDeactivation 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.

IdentityExample consumerWhat must exist
Community NodeRef plus entry CNamequestCharacterMount_ConditionType, questAssignCharacter_NodeType, a vehicle-as-activator triggerA streamed community registry, vehicle entry, spawn set, and placement that resolve together
Direct world NodeRefA trigger or condition whose gameEntityReference.reference names one placed vehicleA stable placed entity in the owning prefab/sector scope
Player-vehicle TweakDB recordquestEnablePlayerVehicle_NodeTypeA player-vehicle record and the player-vehicle system; not an arbitrary world vehicle
Scene actorVehicle staging owned by a sceneA 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:

FieldValue domain
referenceNodeRef of the community registry or compatible entity owner
namesone CName selecting the vehicle entry
typeEntityRef
dynamicEntityUniqueNamenormally 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 fieldFocused valueMeaning
conditionOnMountWait for the mount event/state represented by this condition family
childIsPlayer1The mounted child is V
parentRefthe exact vehicle entity referenceThe designated parent vehicle, not any nearby car
anyParent0Keep the parent bound to that reference
anyChild0Do not broaden the child match
rolechoose deliberatelyDriver, Passenger, or a tested neutral role must match the intended objective
vehicleOrigin, vehicleTypeonly broaden when intendedAny 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 ----/
  1. Activate the objective.
  2. Use questVehicleNodeDefinition with questAssignCharacter_NodeType to bind the contact to the vehicle and seat.
  3. Fan out to one player mount condition and one contact mount condition.
  4. Join them with questLogicalAndNodeDefinition; use two distinct input sockets.
  5. 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:

PropertyOwner
characterRef.referencecontact community NodeRef
characterRef.namescontact entry CName
vehicleRef.referencevehicle community or entity NodeRef
vehicleRef.namesvehicle entry CName
assign1 to assign the contact; 0 is a clear/unassign operation, not an equivalent default
isPlayer0 when characterRef names the contact; 1 changes the subject to V
slotNamea seat slot supported by the vehicle, such as a tested passenger slot
isInstantstaging 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:

FieldRequired contract
triggerAreaRefNodeRef of the placed destination trigger
activatorRefexact vehicle entity reference
isPlayerActivator0
typeIsInside

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

  1. Extract the cited sq004_02_drive.questphase from 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.
  2. 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.
  3. Verify the vehicle and any contact resolve and become spawned before adding assignment or mount waits.
  4. In the child phase, activate the intended objective and mappin, then add one questPauseConditionNodeDefinition per player/contact mount truth.
  5. Bind every gameEntityReference with the exact community/world NodeRef and entry name expected by that condition. Choose the player role and passenger seat deliberately.
  6. Add questAssignCharacter_NodeType only 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.
  7. 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.
  8. Succeed presentation, remove mappins, and write distinct mount/arrival/theft facts before handing off to delivery or cleanup.
  9. 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.
  10. 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:

CaseStart stateExpected observation
Normal mountVehicle and contact not previously activatedCorrect vehicle resolves; only the intended seat/state completes
Wrong vehicleAnother vehicle is beside the targetMounting the other vehicle does not advance
Passenger versus driverV changes seats before the waitThe declared role behaves exactly as documented
First actor unmounts earlyContact or V leaves before the other mount truth occursA simultaneous-occupancy objective does not complete from two historical signals
Fast approachLoad just outside streaming range and approach quicklyReadiness gates prevent lost assignment or mount waits
Reload before mountObjective and mappin activeThe same vehicle identity and wait resume
Reload after factTheft/arrival fact already 1Parent re-entry policy skips or resumes intentionally
Stream away and returnVehicle remains quest-relevantCommunity, entry, marker, and condition still resolve
Removal isolationCandidate archive and loose registrations removed on a dedicated copyNo 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:

FieldFocused value
vehicleRefthe same exact entity reference used by the trigger
comparisonTypeCT_EQUAL in the reduced candidate
speed0

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 sourceLikely ownerSafe question to answer
Community/spawn-set vehicleCommunity and its world placementWhich phase deactivates the entry or community, and after what distance/readiness gate?
Placed world vehicleSector/prefab plus quest lifecycleShould it persist, stream naturally, or receive a resource-specific disable path?
Player-vehicle recordPlayer-vehicle systemIs questEnablePlayerVehicle_NodeType valid for this exact TweakDB record and state?
Scene-staged vehicleScene plus surrounding questWhich 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:

  1. Which resource spawns each pursuer vehicle and occupant?
  2. Which node assigns each character to which seat?
  3. Which AI command begins movement or combat?
  4. Which facts distinguish route and lost-target states?
  5. 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:

ShapeNative type or reference
Start and stop the race subsystemquestStartRace_NodeType, questStopRace_NodeType
Return/recovery routequestGoBackToRace_NodeType plus player-facing restriction/objective state
Competitor movementquestvehicleRacingParams on vehicle command nodes
Course ownershipspline NodeRefs, including a race-specific spline in that phase
Opponent behaviorracing parameters, target refs, precision, push-aside, and rubber-banding policy
Player/vehicle statemount and spawned conditions plus camera and gameplay restrictions
Progress and outcomecheckpoint 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

  1. Choose one bounded activity: stopped delivery, owner-specific cleanup, chase research, or race research. Do not start by combining all four.
  2. Extract only the relevant current-game comparison phase named above and record each inspected node’s containing graph, identities, decisive fields, and surrounding lifecycle.
  3. Author the mod-owned vehicle, occupants, trigger/spline/checkpoint assets, community/world ownership, journal entries, and unique NodeRefs before the quest child.
  4. 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.
  5. 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.
  6. 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.
  7. 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.
  8. 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.
  9. 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.
  10. 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

SymptomFirst boundary to isolate
Delivery completes when V walks inisPlayerActivator or an empty/wrong activatorRef
Correct vehicle enters but never completesVehicle identity differs between trigger and speed condition, or exact-zero semantics never become true
Contact/occupants vanish during routeCommunity activation, streaming coverage, or assignment before spawned readiness
Cleanup removes the wrong vehiclePlayer-record operation used for a community/world vehicle, or broad cleanup shares one owner
Chase starts but cannot finishLost-target/success/stop monitors do not converge or one route never releases the parent
Race UI or restrictions remainStop/restore cleanup is reachable only from the normal finish branch
Reload duplicates opponents or checkpointsSave-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:

CaseRequired result
Wrong vehicle enters destinationNo progress
Correct vehicle passes through without stoppingDelivery remains incomplete
Correct vehicle enters, leaves, then stops outsideNo progress; the sequential structural control should expose its false positive here
Correct vehicle stops insideDelivery succeeds exactly once
Reload while inside but movingThe stopped wait resumes without replaying earlier one-shot work
Reload after delivery before cleanupHandoff and cleanup resume intentionally
Stream away after cleanupNo unintended respawn or remaining marker
Clean-save replay and removal isolationNo 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:

ResourceRequired content
QuestphaseOutcome conditions, branch-specific effects, convergence, reward manager, completion fact, journal success, terminating output
JournalDebrief contact/thread/messages or a debrief objective, plus the quest and objective entries being completed
Onscreen localizationEvery debrief message, reply, objective, and completion-facing string
Reward recordA 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 stateExplicit 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
PropertyContract
typePopulated handle containing questGiveReward_NodeType
type.rewards[]One or more string-backed reward TweakDBID values
socketsIn, Out, and the separate CutDestination
external dependencyEvery 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
StageNative owner
Outcome selectionTwo questPauseConditionNodeDefinition fact waits and a two-input questLogicalXorNodeDefinition
Message presentationquestJournalNodeDefinition → questJournalEntry_NodeType, entered through Active
Player responseOne questJournalEntryState_ConditionType per individual choice, state: Succeeded
Objective/quest presentationSeparate questJournalQuestEntry_NodeType nodes and state sockets
PayoutquestRewardManagerNodeDefinition → questGiveReward_NodeType
Durable completionquestSetVar_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
StepExact node family
1questPauseConditionNodeDefinition → questJournalCondition → questJournalEntryVisited_ConditionType, final gameJournalPhoneMessage path, visited: 1
2questRewardManagerNodeDefinition → questGiveReward_NodeType.rewards[]
3questFactsDBManagerNodeDefinition → questSetVar_NodeType, setExactValue: 1, value: 1
4questJournalNodeDefinition → questJournalQuestEntry_NodeType, entered through Succeeded
5questOutputNodeDefinition, 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

  1. Write the outcome table: source event, durable fact/value, debrief content, payout, cleanup, and fallback.
  2. Author and register all journal/localization content, then verify its full typed paths.
  3. Validate each QuestRewards.* record independently from the quest graph.
  4. Add outcome conditions or a switch and persist the selected result before presentation.
  5. Wire branch-specific debrief messages/scenes and side effects.
  6. Converge at the latest common point; put a shared reward after convergence or distinct rewards before it.
  7. Add the required final gate: visited message, named normal scene output, or another explicitly player-facing completion signal.
  8. Connect reward, completion fact, objective/quest success, cleanup, and termination as distinct nodes in the intended order.
  9. Reopen the resource and inspect every handle, socket, journal path, condition, fact, and reward ID.
  10. Round-trip the mod-owned phase with WolvenKit 8.19.0, then run every outcome from separate copies of one untouched save.

Acceptance matrix

CaseRequired evidence
Every narrative outcomeCorrect debrief content and no content from another branch
Every final responseCorrect reply, one continuation, intended acknowledgement behavior
No/unknown outcomeExplicit fallback; no indefinite hidden wait
Before debrief saveCorrect outcome presentation after reload
After branch message/scene startNo wrong branch, duplicate content, or lost phone/scene state
Before rewardReload pays exactly once when completion continues
Immediately after rewardReload does not duplicate payout or suppress required completion state
Completed saveNo debrief, reward, objective, marker, actor, device action, or root route repeats
Clean replaySame 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

SymptomInspect first
Correct outcome but wrong debriefDurable outcome write, mutually exclusive values, switch/case mapping
Both outcome messages appearLosing wait, impossible dual facts, stale save, convergence wiring
Final message appears but completion stallsVisited path/class/index, named scene output, or deliberate pacing gate
Reward node runs with no payoutReward record existence/content, exact TweakDBID, framework logs
Reward repeatsMultiple completion routes, interruption window, missing payout/completion guard
Quest succeeds before dialogue finishesActivation or pacing signal was mistaken for acknowledgement/normal scene completion
Quest completes with actors or marker presentCleanup owner is absent or ordered after termination
One old save routes differentlySaved 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:

  1. Ownership and resource chain — which resource owns quest flow, rewindable screenplay, recorded animation, support entities, markers, clue state, and cleanup.
  2. 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.
  3. 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 pathFocused question
base\quest\side_quests\sq012\scenes\sq012_02a_braindance.sceneHow 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.sceneridHow 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.entThe visual-layer support entity selected by a scene prop record.
base\quest\side_quests\sq012\entities\sq012_02a_braindance_bdfog.entThe fog support entity selected by a scene prop record.
base\quest\side_quests\sq012\entities\sq012_02a_braindance_bdsetup.entThe render-to-texture setup entity selected by a scene prop record.
base\quest\main_quests\prologue\q004\scenes\q004_05_bd_yorinobu.sceneA 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.questphaseThe 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.

RecordValue
Research review2026-08-09
Game archives inspectedCyberpunk 2077 Windows GOG 2.31a
Inspection and structural round tripWolvenKit 8.19.0
Practical framework baselineArchiveXL 1.27.0, RED4ext 1.30.0, redscript 0.5.31
Runtime statusExperimental — 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/.scenerid research chain is Structurally validated because each binary serialized and deserialized with WolvenKit 8.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

OwnerWhat it ownsWhat it does not prove
ArchiveXL root registrationThe one root questphase visible to the quest loaderThat external child phases, scenes, RID files, or world nodes resolve
questQuestPhaseResourceEntry flow, preparation, scene invocation, exit branch, post-scene cleanup, journal/fact handoffRewindable timeline semantics or recorded animation buffers
questSceneNodeDefinitionSoft scene depot path, scene input, world marker, and named output socketsThat the scene can acquire all actors/props or play its events
scnSceneResourceActors, player actors, props, workspots, screenplay store, scene graph, entry/exit points, interruption scenarios, RID handlers, and resource-reference tablesThe bytes for recorded actor/camera motion or world placement
scnRidResource in .sceneridActor and camera tags, serial numbers, body/facial/cyberware channels, camera channels, compressed buffers, duration, joint/track cardinalityScene performer assignment, clue logic, entry/exit, or cleanup
scnPropDef plus TweakDB recordHow a scene acquires/spawns a support prop such as BD view, fog, setup, or cameraThe world marker at which it appears
Streamed world resourcesScene origin, support-prop marker, camera entity, clue entities, player hold/return anchors, and their transformsScene or quest execution
scneventsClueEvent and scene quest nodesLayer, active time interval, clue entity, discovery operation, and fact handoffJournal state unless the quest consumes the result
SaveFacts, journal progress, active quest/scene state, and other persistent side effectsA 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 areaCurrent focused observation
entryPoints / exitPointsTwo named entries and one named exit bind names to scnNodeId values. Names, not editor position, form the quest handoff.
interruptionScenariosOne enabled distance-based scenario has separate interrupt and return thresholds. This is not proof of custom interruption cleanup.
ridResourcesThree handler entries each pair a typed resource ID with an exact .scenerid depot path.
resouresReferencesRID body, facial, cyberware, and camera reference arrays sit beside ordinary gameplay/cinematic/lipsync sets.
propsBD view, fog, setup, and camera are separate props rather than flags on the rewindable node.
sceneGraphOne 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.

  1. Define the terminal quest outcome, replay policy, and cleanup obligations before creating animation.
  2. 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.
  3. Create or place the scene origin, support-prop marker, camera entity, clue entities, and player hold/return anchors in mod-owned world resources.
  4. Declare those world roots in the exact questphase that resolves them.
  5. Build an ordinary scene shell first: actors, props, entry, exit, interruption scenario, and a non-rewindable diagnostic route.
  6. Add a mod-owned .scenerid; inventory every actor/camera tag, animation serial, channel, duration, joint count, and track count.
  7. Add scene RID handlers and reference tables, then bind events by typed reference ID. Do not infer array indexes from another scene.
  8. Add the rewindable section, its timed events, layer conditions, clue events, and named outputs.
  9. Add quest-side preparation, scene invocation, normal cleanup, interrupted cleanup, journal/fact handoff, and replay guard/reset.
  10. 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

SymptomInspect first
Scene node never startsRoot registration, child resource path, input name, phase prefab root, scene origin, and actor readiness
Scene starts but recorded bodies do not moveRID handler path/ID, actor signature and serial, scene RID-reference row, event animResRefId, performer mapping, duration, and joint cardinality
Recorded camera is absentCamera RID tag/serial, scene camera-reference row, camera event animSRRefId, camera NodeRef, camera entity, origin marker, and activation flag
View/fog/setup layer is missingScene prop record, corresponding .ent, support marker, prop spawn policy, visibility events, and stream availability
Clue appears but quest does not advanceLayer/time interval, clue entity reference, discovery node, fact name/value, and quest-side fact wait
Exit leaves V or UI in the wrong stateNamed output route, normal/interrupted cleanup symmetry, teleport/restore ordering, UI/input restoration, and save history
Replay uses stale resultsDiscovery 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.scenerid and sq012_02a_braindance.scene are Observed in vanilla on Cyberpunk 2077 2.31a, re-extracted and inspected with WolvenKit 8.19.0 on 2026-08-09. The retained mod-owned RID/scene pair round-trips with WolvenKit 8.19.0 and 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 .scenerid ResourcePath.

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:

FieldRole
nextSerialNumberNext 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 fieldRecord
RID depot pathLocates the recorded animation resource
RID root versionPrevents assumptions from another layout version
Actor/camera signatureNames the intended RID slot
Tag serial and animation serialProves RID-local identity
ChannelBody, facial, cyberware, or camera
Duration and frame rate basisSupports event timing comparison
Joint/track counts and ordered rig contractDetects compatible-looking but wrong buffers
Motion-extraction/root policyDistinguishes trajectory motion from in-place pose
Source and conversion versionsMakes 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);
  • animOriginMarker with a global scene-marker NodeRef;
  • actorPlacement: SceneOrigin;
  • startTime and duration;
  • 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:

PropertyFocused meaning
animSRRefIdScene-local index into a RID-camera reference array
cameraRefFull world NodeRef for the camera entity
animOriginMarkerMarker defining the recorded coordinate origin
cameraPlacementPlacement policy such as SceneOrigin
activateAsGameCameraWhether the event takes over the game camera
controlRenderToTextureStateWhether this event controls that state; not equivalent to the BD setup prop
startTime / durationTimeline interval that must agree with the recorded channel
markCamerCutCut 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, and 6;
  • backward slow/fast/very-fast modifiers 0.5, 3, and 6;
  • 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:

  1. Is it a pure function of current timeline time, or does it cause a durable side effect?
  2. What state must be reconstructed when the playhead seeks into its middle?
  3. What is undone when the playhead moves backward across its start?
  4. Does its end state differ when playback jumps past the whole interval?
  5. Does it target an actor, prop, camera, clue, VFX, UI state, fact, or socket?
  6. 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.

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:

  1. In the scene, list each ridResources[] handler’s resource ID and depot path.
  2. In resouresReferences, list each RID animation/camera reference’s resource ID and serial number.
  3. In the RID, find the corresponding actor/camera tag and animation serial.
  4. In the rewindable section, select one body event and one camera event; record their local reference IDs, performer/camera, origin, start, and duration.
  5. Verify that the event’s local reference resolves to the expected RID resource/serial pair.
  6. 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

  1. Finish the ordinary scene actor/prop acquisition and named entry/exit contract before introducing RID playback.
  2. Add the mod-owned RID resource and inventory all tags, serials, channels, durations, joint counts, and track counts.
  3. Allocate a unique scene scnRidResourceId and add its handler/path.
  4. Add scene-level body/facial/cyberware/camera references, pairing the exact resource ID with exact RID animation serials.
  5. Add one short body event and verify its performer, origin, placement, duration, and local reference ID structurally.
  6. Add one short camera event and verify its camera entity and marker chain.
  7. Only then add the full rewindable section and the remaining channels.
  8. Re-inspect every reference ID after array edits; do not assume earlier indexes stayed stable.
  9. Serialize to CR2W, serialize the binary back to JSON, and compare root type, RID handlers, serial mappings, event references, durations, and cardinalities.
  10. 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

  • .scenerid root 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

SymptomInspect first
Nothing moves but the scene advancesRID handler path/ID, scene reference resource/serial, event local ID, performer, and event interval
Wrong actor receives motionRID signature/serial map, scene performer map, and event performer
Actor snaps or driftsScene marker transform, recorded root basis, motion extraction, placement policy, and duration/stretch
Body works but face or cyberware does notSeparate channel presence, joint/track cardinality, scene anim-set reference, actor component, and event binding
Camera remains gameplay cameraRID camera serial/reference, event local ID, cameraRef, activateAsGameCamera, marker, and camera entity
Forward playback works but seeking breaksEvent reconstruction at arbitrary time, side effects crossed by seek, interval coverage, and replayable state
Binary round-trips but crashes or corrupts at runtimeBuffer 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.

RecordValue
Research review2026-08-09
Game / inspectionCyberpunk 2077 Windows GOG 2.31a; WolvenKit 8.19.0
Practical framework baselineArchiveXL 1.27.0; RED4ext 1.30.0; redscript 0.5.31
Custom runtime evidenceExperimental — 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:

PropertyResponsibility
clueName (CName)Scene-local semantic name for the clue
layerAnalysis layer such as Visual or Audio
startTime / durationInterval during which the clue belongs on the recording timeline
clueEntity (gameEntityReference)Target actor/community entry or standalone world NodeRef
markedOnTimelineWhether the event contributes a timeline marker
factName / overrideFactOptional event-owned fact behavior; do not assume every vanilla clue uses it
type, executionTagFlags, scalingDataAdditional 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:

  1. Which analysis layer is currently selected?
  2. Is the playhead inside this clue’s time interval?
  3. Is the clue entity available and focusable?
  4. 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:

  1. Place or acquire one mod-owned clue entity and give it a fresh full NodeRef or actor/community reference.
  2. Add one scneventsClueEvent to the rewindable section with an interval entirely inside the section duration.
  3. Set the exact analysis layer and decide whether the clue is marked on the timeline.
  4. Add the layer/interval/focus path and one discovery operation.
  5. Write one dedicated fact and wait for that exact fact in the owning questphase before changing journal state.
  6. Add the clue’s temporary focus/highlight state to both normal and interrupted cleanup ledgers.
  7. Serialize the .scene to CR2W, reopen or serialize it back to JSON, and verify that event identity, layer, interval, entity reference, and fact survived.
  8. 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.

StateTypical ownerNormal-exit policyInterrupted-exit policyReplay policy
Selected layer / playback speedBD scene/runtime UIResetResetStart from documented default
Gameplay camera and render-to-texture stateScene events/support entitiesRestoreRestoreReacquire from clean state
Input/UI/action masksQuest/scene managersRestoreRestoreReapply only on entry
Player hold/return placementQuestphase/world anchorsReturn safelyReturn safelyRe-enter through entry gate
Spawned view/fog/setup/camera propsScene acquisitionDespawn/releaseDespawn/releaseFresh acquisition
Temporary clue focus/highlightScene/clue systemClearClearReconstruct from playhead/layer
Discovery factFacts databasePreserve or reset by explicit designSame explicit policyMust match documented replay design
Journal objective stateJournal resource/saveCommit only at defined handoffDo not accidentally commitResume or remain complete by explicit design
Active scene/phase stateScene and questphase/saveReach named exit/terminal outputReach interruption cleanup/outputMust 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.

CaseStart and actionRequired observationsFailure boundary
Case 1 — forward seekClean seed-before-entry; enter normally, then seek from an early time to a later time crossing actor, camera, and clue intervalsCorrect reconstructed actor/camera/prop state at destination time; no duplicated durable side effect; controls remain responsiveOrdinary linear playback is not a forward-seek pass
Case 2 — backward rewindSeparate clean clone; play to a later interval, rewind across camera/body/clue boundaries, then resumeEarlier pose/camera/visibility/focus state is reconstructed; no stuck later effect; resume remains coherentMoving the playhead without reconstructing state fails
Case 3 — visual layerSeparate clean clone; select visual layer during a visual clue interval and discover itOnly intended visual clue is available; timeline/entity/focus agree; exact visual fact changes once; journal does only the documented workSeeing a highlight without fact/journal proof fails
Case 4 — audio layerSeparate clean clone; select audio layer during an audio clue interval and discover itIntended audio clue and playback/focus behavior appear; exact audio fact changes once; other facts remain unchangedHearing scene audio is not an audio-clue pass
Case 5 — thermal layerSeparate clean clone; select thermal layer during a thermal clue interval and discover itIntended thermal clue is available only under the documented conditions; exact thermal fact changes onceA visual/VFX approximation is not a thermal-layer pass
Case 6 — normal cleanupSeparate clean clone; discover required clues and leave by the normal scene outcomeCamera, render state, UI/input, player placement, performers, support props, highlights, quest child, facts, and journal match the ownership ledger; save/reload remains stableReaching the next objective while leaking temporary state fails
Case 7 — interrupted cleanupSeparate 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 stableTesting interruption before playback acquires anything is insufficient
Case 8 — replay after cleanupLoad seed-after-normal-cleanup made by this exact candidate; invoke the documented replay/resume routeReplay eligibility matches policy; scene starts from a coherent state; preserved discoveries and reset temporary state behave exactly as documented; second cleanup succeedsRestarting 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

SymptomInspect first
Correct layer, no focusable clueEvent interval, layer, clue entity reference, entity streaming/acquisition, and focus toggle
Clue highlights but fact remains zeroDiscovery node/event path, exact fact name, overrideFact, signal ordering, and save history
Fact changes but objective does notQuestphase fact condition, logical join, journal path/state, and one-shot guard
Rewind leaves highlight/VFX behindEvent interval reconstruction, layer condition, clue/VFX end state, and side effects crossed by reverse playback
Normal exit leaves BD presentation activeView/fog/setup prop cleanup, camera/render restoration, UI/input masks, and named outcome route
Interrupted exit strands the playerCut route, scene ownership termination, player return anchor, and cleanup ordering
Replay skips or instantly completesPreserved discovery facts, journal state, completion fact, active scene state, and explicit replay gate/reset
Old and new builds behave differently on the same saveThe 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:

ComponentVersion
Cyberpunk 2077Windows GOG 2.31a
WolvenKit8.19.0
ArchiveXL1.27.0
RED4ext1.30.0
redscript0.5.31

Use the tested versions when reproducing a problem, and record any differences in your setup.

Route by symptom

SymptomStart hereFirst question
A file saves or packs, but content is absent or the game crashesSerialization versus runtimeDid the cooked binary retain the authored topology and scalar values?
The root never starts, or a child/resource reports missingRegistration and depot pathsIs 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 failsHandles, sockets, and resourcesIs this an internal handle, graph socket, or external resource reference?
A trigger, marker, actor, or device is unresolved or location-dependentNodeRefs, streaming, and placementDoes every local/full NodeRef join reach a loaded concrete world node?
A scene crashes at launch, an actor is missing, or dialogue never startsActors, scenes, and lipsyncWas 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 staleJournal, localization, and audioWhich 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 stateSave state and clean retestsWhat state did the save already contain before these bytes were installed?
Several changes landed together and causality is unclearControlled isolation and evidenceWhat 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.

LayerDecisive evidenceIf it fails
Authored resourceExact node/resource/property inventoryCorrect the WolvenKit project before cooking
CR2W round tripSerialized output retains semantic topology and valuesRepair the resource; do not continue to packing
Archive payloadArchive listing and extracted payload hashesCorrect project paths or stale build output
Loose framework filesInstalled .archive.xl and dependency inventoryCorrect staging before opening a save
RegistrationArchiveXL/RED4ext/redscript logsCorrect the exact path or registration class
Native lookupJournal path, ResourcePath, NodeRef, actor, or localization joinTest the relevant ownership chain
Runtime lifecyclePlayer-facing oracle plus logsTest readiness, ordering, cleanup, and interruption
Save restorationNamed starting save and reload observationsReturn 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:

  1. Close the game and framework processes.
  2. Record the installed mod and framework inventory.
  3. Hash the .archive, .archive.xl, and every other candidate-owned loose file.
  4. Preserve the full relevant logs and hash them before the next launch overwrites or appends to them.
  5. Record the starting save slot and whether it predates the first candidate.
  6. Write the expected player-facing result and the exact observed deviation.
  7. 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

SymptomLikely boundaryDecisive test
Converter exits zero but nodes are missingCR2W object-graph/topology writeSerialize the produced binary back and compare semantic node counts and edges
Graph shape matches but a path is still a template tokenNested scalar writeSearch the round trip for unexpected placeholders and compare decisive properties
Archive packs but root never startsRegistration or depot pathList/extract the archive, then inspect ArchiveXL registration logs
ArchiveXL registers everything but load crashesNative resource content or runtime lookupReduce to the earliest activation boundary; inspect scene/world/resource cardinalities
Behavior differs after reinstalling identical bytesSave-backed stateCompare 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 fileEntryIndex values;
  • 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

  1. Save the resource in WolvenKit and close its editor tab so the binary on disk is final.
  2. Convert or serialize that binary to an isolated review directory.
  3. Compare it with the authored inventory. Start with counts, type sets, sockets, edges, and decisive values rather than a full textual diff.
  4. Reopen the binary in WolvenKit and inspect the same nodes and nested properties.
  5. Pack only after the focused comparison passes.
  6. List and extract the candidate archive to a new directory.
  7. Hash each extracted payload against the project output and record the candidate archive hash.
  8. 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:

  1. Return to the last structurally correct resource.
  2. Recreate only the first missing object or value in WolvenKit.
  3. Save and round-trip immediately.
  4. Verify all existing topology as well as the new edit.
  5. 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

ResultSupported label
Binary round trip and semantic checks passStructurally validated
Same shape exists in a cited vanilla resourceObserved in vanilla
Exact unchanged candidate passes the defined game routeRuntime-proven
Candidate is unplayed, reload cases are incomplete, or several variables changedExperimental 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
EdgeExample ownerFailure
Packed payload.archive depot treeResource does not exist at the path requested
Loose configuration.archive.xl beside the archiveArchiveXL never sees the registration instructions
Root attachmentquest.phases, journal, localization, or streaming.blocksRoot system does not expose the mod resource
Nested resolutionphaseResource, subtitle map, block descriptor, or another CR2W referenceRoot 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

  1. Close the game and every framework process.
  2. Inventory archive\pc\mod for every candidate-owned .archive and .archive.xl. Hash every intended and duplicate file before changing the live directory.
  3. Move or disable every unintended live copy into a named quarantine outside all active mod/framework paths. Do not delete the preserved evidence.
  4. Inventory archive\pc\mod again and require exactly the intended candidate pair, with no renamed or older copy that can still mount.
  5. Hash the intended pair and compare it with the built candidate.
  6. List the archive and confirm the exact root depot path appears once.
  7. Extract that entry and compare its hash with WolvenKit project output.
  8. Open the .archive.xl as text and compare its path character for character with the archive listing.
  9. Launch to the load menu and preserve fresh RED4ext, ArchiveXL, game, and redscript logs.
  10. Search for the candidate filename, resource path, and the first missing dependency. Record an error’s full context rather than one line.
  11. 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:

SymptomInspect next
Parent enters a phase node and stopsphaseResource.DepotPath, soft reference form, child archive entry, and child In1 interface
Journal entries never renderJournal registration, full gameJournalPath.realPath, fileEntryIndex, and entry state socket
Text is blankCorrect localization registration class and exact secondary key or numeric RUID
World objective has no trigger/markerRegistered streaming block, descriptor sector path, prefab root, and child NodeRef chain
Scene starts but line has no text/audioSubtitle-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:

ResponsibilityRegistration surface used in the labs
Executable root questphasequest.phases with mod path and vanilla parent
Journal treejournal
Journal/UI textlocalization.onscreens for the locale
Spoken subtitleslocalization.subtitles for the locale
Spoken audio lookuplocalization.vomaps for the locale
World loadingstreaming.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 fixWhy it misleads
Register every CR2W as a rootChanges ownership and may start resources independently
Copy an extracted vanilla parent into the archiveCreates a global override and redistributes game content
Rename only the project fileLeaves ArchiveXL and nested ResourcePaths pointing at the old depot path
Edit the graph until a log error disappearsRegistration occurs before the affected runtime branch
Test only an old completed saveIts 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

ConnectionTypical formScopeQuestion
CR2W handleHandleId / HandleRefIdOne serialized CR2W object graphWhich object does this field own or reference?
Quest graph edgeSource socket to destination socketOne questphase graphWhich output causes execution to enter which input?
Scene graph stampNamed/ordinal socket stamps and destinationsOne scene graphWhich stamped route joins these nodes?
External resourceResourcePath, often hard or softVirtual depotDoes the packed/registered resource exist at this exact path?
World identityNodeRefStreaming/world namespaceIs 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.

  1. Serialize the binary back to an isolated review file.
  2. Inventory every HandleId and every HandleRefId in the affected CR2W.
  3. Require each reference to resolve to exactly one compatible object.
  4. Check ownership: two fields may intentionally reference one object, but a copied node must not accidentally alias the old node’s sockets or payload.
  5. Inspect the target object’s RED type, not only the matching number.
  6. 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:

  1. the source socket is an output or the node type’s corresponding source role;
  2. the destination socket is an input with the intended operation semantics;
  3. spelling and case match the concrete socket contract;
  4. both socket handles still belong to their intended nodes after round trip;
  5. the source connection targets the destination socket, not merely the destination node;
  6. a parallel fan-out is deliberate and does not masquerade as an ordered sequence;
  7. a CutDestination route 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:

  1. copy the exact ResourcePath from WolvenKit;
  2. identify whether the reference is hard, soft, or reached through a registered map/block;
  3. list the archive and confirm the target path exists;
  4. inspect any intermediary owner, such as a subtitle map’s entries path or a streaming block descriptor’s sector path;
  5. check ArchiveXL logs for registration/merge errors;
  6. 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

FailureSmallest safe correction
Dangling handleRecreate the referenced nested object through WolvenKit and reselect it from the owning property
Wrong shared ownershipDuplicate through an editor operation that creates an independent object, then rewire its fields
Missing graph edgeConnect the exact named source and destination sockets
Wrong operation socketReconnect to the intended semantic input, such as Succeeded rather than Active
Missing external pathCorrect the ResourcePath or pack/register its target; do not alter internal handles
Indexed table too shortSupply 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

SymptomFirst diagnosisDecisive test
Trigger never fires anywhereBroken identity chain, notifier, activator contract, or condition stateTrace local child, root, descriptor, full child, concrete trigger/notifier, and consuming condition
Object works only when already nearbyStreaming descriptor/bounds or lifecycle dependencyStart outside, approach normally, then stream away and return on a controlled save
Marker exists but quest cannot find itMarker identity differs from quest NodeRefCompare exact local/full paths and phase-prefab owner
Quest finds a trigger but it is in the wrong placePlacement transform or trigger geometryInspect position, rotation, scale, and shape points in the sector
Mappin log cannot resolve positionUI lookup cannot resolve the target identityUse a proven marker strategy and keep gameplay/UI identities explicit
Device looks correct but old behavior persistsSave-backed controller identityRetest from pre-install save; for deliberate identity invalidation, use a fresh NodeRef
Fast travel crashes while ordinary approach worksStreaming-time activation race or malformed streamed resourcePreserve crash boundary, test block/sector alone, then reintroduce quest activation

Check loading before logic

  1. Confirm ArchiveXL registers the exact streaming block path.
  2. Confirm every descriptor’s sector path exists in the archive.
  3. 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.
  4. Confirm questPrefabNodeRef is present where the world-side root binding is required.
  5. Confirm the sector registers each full child in nodeRefs.
  6. Inspect the associated concrete node and its node data.
  7. For a trigger, inspect its questTriggerNotifier_Quest: require the intended isEnabled, includeChannels, and excludeChannels values.
  8. Inspect the consuming questTriggerCondition: match triggerAreaRef and predicate type, and verify isPlayerActivator plus activatorRef. Labs 3–5 use isPlayerActivator: 1 with the exact empty entity-reference payload; do not substitute an arbitrary actor NodeRef.
  9. 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:

ResponsibilityPossible owner
Device reservation/interactionNative or mod-owned device NodeRef
Quest conditionTrigger or device NodeRef under the quest prefab
In-world scene placementScene marker NodeRef
Journal/map presentationDedicated 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:

  1. load a named pre-activation seed outside the finite descriptor;
  2. approach normally and verify one activation;
  3. stream away by ordinary movement, return, and check reacquisition without duplication;
  4. load a separate save captured while the world-dependent stage is active;
  5. finish and verify cleanup;
  6. 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 phasePrefabs across 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:

LayerCommunity actorPlayer actor
Spawn ownerActive community entry/phaseExisting player context
Quest readinessCharacterSpawned condition on the same community targetUsually not a community-spawn gate
Scene acquisitioncommunity with matching area/entryfindInContext with the intended player record
Scene actor IDActor-table key used by screenplay linesSeparate actor-table key
Performer IDSection/event/debug-symbol performerMust map to the correct actor

Then test this order:

  1. activate the community;
  2. wait for CharacterSpawned on the exact community NodeRef/entry scope;
  3. enter a broad setup boundary;
  4. checkpoint if the tested pattern requires it;
  5. start the scene;
  6. 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:

  1. confirm the screenplay item’s speaker and addressee are valid actor IDs;
  2. confirm the section’s actor behavior rows use the intended performer IDs;
  3. confirm debug-symbol performer mappings agree with the actor table;
  4. confirm a community actor’s area NodeRef and entry name match the active registry entry exactly;
  5. confirm the scene marker exists and the quest scene node uses the correct entry point CName;
  6. 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:

  1. 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.
  2. 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.
  3. Restore one actor-acquired section without audio/choices.
  4. Verify resource-table cardinalities and actor joins.
  5. Restore the line and external localization/audio path.
  6. 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

SymptomInspect first
Actor never appearsCommunity registry/entry/phase, AI spot, activation, record/appearance namespace
Actor appears but scene waits foreverCharacterSpawned target/scope and scene acquisition entry
Crash exactly when scene launchesActor table, performer joins, resource-reference indexes, graph entry
Slow approach works, fast approach failsReadiness/order race and trigger cascade
Dialogue plays but actor is invisibleCharacter root/default appearance and requested community appearance
Head stretches during a lineSelected lipsync clip, additive skeletal reference, and rig/track ordering
Scene exits immediatelyBypassed section or retained Start-to-End edge
Scene never returns to questScene End/named exit and questphase output CName mismatch
No subtitle or audio but no crashContinue 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 contentAuthoritative keyRuntime owners
Quest title, objective, mappin, message, shard, onscreenTextual secondary keyJournal entry plus ArchiveXL-registered onscreen localization
Spoken subtitleUnsigned numeric localization RUIDScene line plus subtitle map plus subtitle entries
Spoken audioSame unsigned numeric RUIDScene line plus VO map plus WEM ResourcePath
Player choice labelScene-local numeric locstring IDChoice 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:

  1. Confirm the journal resource is packed and registered under the journal registration class.
  2. Copy the exact full gameJournalPath.realPath from the quest node.
  3. Split the path into components and verify each parent/child ID in the journal tree.
  4. Verify fileEntryIndex identifies the component represented by the containing gameJournalFileEntry; it is not the objective’s child index.
  5. Confirm the quest edge enters the correct journal node socket, such as Active, Succeeded, Failed, or Inactive.
  6. Check the starting save for an existing state or visited flag.
  7. Confirm the localized key exists only after the state transition itself is known to occur.
SymptomLikely first boundary
No entry and no state transitionRegistration, path, fileEntryIndex, or graph socket
Entry exists with blank titleOnscreen localization registration/key
Objective activates but pin does notMappin journal child/state and world marker lookup
Entry is already succeeded on first testStarting save or an earlier candidate’s graph state
File/shard exists but read gate never advancesExact 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.onscreens locale registration;
  • exact packed resource path;
  • primaryKey/secondaryKey shape 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:

JoinExpected equality
Scene screenplay linelocstringId.ruid equals the authored spoken-line RUID
Subtitle mapRegistered map points to the exact subtitle-entries ResourcePath
Subtitle entrystringId equals the scene RUID
LocaleMap 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:

  1. confirm localization.vomaps registration for the tested locale;
  2. confirm the VO entry stringId equals the scene RUID;
  3. confirm female and male ResourcePaths intentionally target existing WEMs;
  4. list/extract the archive and hash each WEM;
  5. confirm the game follows the expected gender path;
  6. 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;
  • locstringId values are sorted numerically as unsigned integers within the arrangement being copied, not lexicographically as decimal strings;
  • duplicate descriptors preserve their intended relative mapping;
  • every vpeIndex remains zero-based and points to the matching payload;
  • descriptor and payload variantId values 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

TestWhat a pass provesWhat remains unproved
Journal state changes with placeholder-visible debug inspectionGraph path/state operationOnscreen localization and player presentation
Onscreen title/objective rendersJournal/UI lookupScene subtitle, audio, and choices
Subtitle renders with audio disabled/unchangedScene-to-subtitle pathVO-map/WEM path
WEM plays with matching subtitleSpoken-line external joinsChoice locStore
Every choice label maps correctlyEmbedded locStore chainBranch 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 classSuitable useNot suitable for
Untouched pre-install originalFirst activation and clean replayRoutine play or destructive diagnosis
Closed-game full-slot clone of the originalIndependent clean casesClaiming a mid-flow resume state
Named mid-flow seed made under one exact candidateReload/stream/interruption cases defined at that pointFirst-install claims or a different candidate hash
Completed seedGuard/reinstall/removal isolationReplaying first activation
Old or unknown saveNegative diagnosis onlyPositive 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

  1. Exit to desktop and wait for the game process to close.
  2. Record the source slot-directory name and visible save label.
  3. Hash the source slot’s canonical save artifact and record any metadata used by the lab acceptance form.
  4. Copy the complete slot directory, not selected files, to a new unique slot.
  5. Hash the clone and verify it matches the source capture before launch.
  6. Assign that clone to one execution only.
  7. 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

DomainExample symptom after replacement
Fact databaseCompletion guard still bypasses the quest
JournalObjective remains active/succeeded or a message stays visited
Quest graph/checkpointExecution resumes after the edited node instead of entering it
SceneActive/interrupted state restores an older section or actor acquisition
CommunityEntry remains active, spawned, or already cleaned up
Streaming worldActive world-dependent stage restores at a different load boundary
Device persistent stateController 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:

  1. Hash the exact installed candidate and logs.
  2. Load a clone of the untouched pre-install original and follow the shortest route to the symptom.
  3. Exit fully; preserve observations and fresh logs.
  4. Relaunch the same bytes and load the old/unknown save.
  5. Compare the first visible divergence in facts, journal, active objective, actor/world state, or device state.
ResultInterpretation
Clean clone passes, old save failsSaved lifecycle is the leading boundary; do not rewrite structurally identical resources yet
Both fail at the same pointSave contamination is not sufficient to explain the failure
Old save passes, clean clone failsOld state may bypass a broken first-run route
Results differ across launches with the same save and bytesPreserve 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:

  1. Clean activation: untouched clone enters the first-run route once.
  2. Mid-flow reload: a named seed resumes without duplicate activation or skipped required work.
  3. Completed reload: completion state persists and the root guard bypasses replay.
  4. Identical reinstall: reinstalling byte-identical candidate files does not erase completion.
  5. Clean replay: another clone of the original repeats the first-run route.
  6. 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

  1. Preserve the failing candidate, save metadata, and logs.
  2. Close the game.
  3. Restore the exact intended installed files and verify hashes.
  4. Restore or clone the correct save capture.
  5. Launch once and collect a fresh complete log bundle.
  6. 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:

InputFrozen value
Game/framework stackExact versions and storefront/build
Candidate filesNames, sizes, SHA-256 hashes, and install paths
Archive contentsSorted depot-path listing and extracted payload hashes
Unrelated modsExplicit inventory or verified absence
Starting saveCapture name, slot directory, pre-install assertion, and hash
RouteExact player actions and activation boundary
OracleVisible/log result that distinguishes pass from fail
LogsComplete 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

  1. Copy the working baseline project.
  2. Make one semantic change. Generated binary noise does not count as another semantic change, but it must still be inspected.
  3. Round-trip every changed CR2W and compare decisive properties.
  4. Build in a clean staging directory.
  5. List/extract the archive and prove the expected payload set.
  6. Compare extracted payloads with the baseline and name every changed depot path.
  7. Stage one canonical candidate; record that earlier labs/copies and unrelated mods are absent.
  8. 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:

RunInstalled bytesSave clonePurpose
Baseline reproductionBaseline hashClone AProve the original symptom still occurs
Candidate testCandidate hashClone BTest the one-delta hypothesis
Baseline return, when practicalBaseline hash restoredClone CReject an unrelated one-time environment change
Removal isolation, when definedCandidate pair deliberately absentClone DProve 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:

FieldExample
ExpectedScene reaches its first section without process termination
ObservedCrash at setup boundary; no dialogue visualizer; report ID retained
DifferenceCandidate moved past setup and returned through named exit
InterpretationResult is consistent with the indexed table diagnosis
Rejected alternativesWorld/trigger path was byte-identical; subtitle/VO resources unchanged
LimitationsShared 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:

  1. Which exact bytes should be installed?
  2. Which files and mods must be absent?
  3. Which save capture and clone should be used?
  4. What actions reach the tested boundary?
  5. What observation passes or fails the case?
  6. Which logs and evidence must be retained?
  7. 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

NeedReference
Choose the next practical project or download a checkpointLabs at a glance
Decode a RED or book-specific termGlossary
Find which resource owns a responsibilityResource map
Distinguish handles, NodeRefs, facts, paths, keys, and numeric IDsIdentifier-domain quick reference

Graph and condition lookup

NeedReference
Find a quest/scene node, its sockets, and the lab that demonstrates itNode and socket index
Choose a condition family without confusing a predicate with a waiting nodeCondition index
Read node IDs, sockets, edges, and colors in the diagramsReading graph diagrams

Registration and data lookup

NeedReference
Build or diagnose a typed journal path and fileEntryIndexJournal-path reference
Trace UI, subtitle, VO, or embedded scene-choice localizationLocalization reference
Decide what ArchiveXL registers and what the archive reaches indirectlyArchiveXL registration reference
Connect a status to a custom visual on V or an NPCEntity spawners and status effects
Reuse the tutorial naming convention without colliding with itTutorial namespace

Additional references

NeedReference
Check which labs have been tested in gameLab status
Check the exact pinned practical versionsTested versions
Locate a named vanilla resource to extract and inspect yourselfVanilla 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

LabCanonical title and namespacePrimary boundaryBuild and test
1First Signal · cqa001Root registration, journal state, real-time delay, one-shot factOverview · Author · Test
2Signal Race · cqa002Immediate selection, pause conditions, parallel listeners, XOR-shaped convergenceOverview · Author · Test
3Boundary Check · cqa003Streaming block/sector ownership, NodeRefs, marker, nested trigger volumesOverview · Author · Test
4Handoff Point · cqa004Registered root, archive-resolved external child, In1/Out1 handoffOverview · Author · Test
5First Contact · cqa005Community lifecycle, actor acquisition, one-line scene, named exit, cleanupOverview · 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

LabCompleted project/file stemStart ZIPCompleted ZIP
1CQA_Lab01_OneShotDownloadDownload
2CQA_Lab02_SignalRaceDownloadDownload
3CQA_Lab03_BoundaryCheckDownloadDownload
4CQA_Lab04_HandoffPointDownloadDownload
5CQA_Lab05_FirstContactDownloadDownload

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

TermMeaningContinue with
ActorA 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 spotA 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
ArchiveThe 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 registrationLoose 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 questphaseA 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 saveFor 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
CNameA 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
CommunityThe 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 areaA 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
ConditionIn 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 payloadThe 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
ConnectionA 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
CR2WREDengine’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 handleA serialization-local HandleId / HandleRefId relationship between objects in one CR2W object graph. It is not a quest node ID or NodeRef.Identifier domains
CR2W-JSONWolvenKit’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
CutAn 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

TermMeaningContinue with
Depot pathA 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
DescriptorIn 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 pointA 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 labelOne of exactly four claim classes: Runtime-proven, Structurally validated, Observed in vanilla, or Experimental.Claim labels
External childThe 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
FactA 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-outSeveral 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 IDThe 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 resourceA 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 entryA 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 pathA 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
JoinA 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 keyThe 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 RUIDThe 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

TermMeaningContinue with
MappinMap/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 resourceThe 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
NodeRefA 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 dataA 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
OrdinalsNode-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 ConditionA 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
PerformerA 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 prefabA 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 prefabThe 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
QuestphaseA .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
RaceSeveral active execution routes converging on a winner-shaped node. An XOR-shaped topology is not evidence that losing listeners are automatically cancelled.Signal flow
ResourcePathThe typed serialized value used to address another depot resource. A soft ResourcePath is a reference-loading policy, not ArchiveXL registration.Identifier domains
Root questphaseThe 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

TermMeaningContinue with
Save-backed stateRuntime 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
SceneA .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 itemA 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
SocketA 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 stampA 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 referenceA 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 blockA worldStreamingBlock whose descriptors point to sectors and carry their streaming metadata. ArchiveXL registers the block, not each sector separately.Streaming model
Streaming sectorA worldStreamingSector that owns or references concrete world nodes, placement records, and registered NodeRefs. Its arrays are not universally parallel.Sector nodes and placement
Terminating outputA 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
TweakDBIDA 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
WEMA 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
WolvenKitThe 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
WorkspotA 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:

LabelMeaning
Runtime-provenThe 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 validatedThe resource serialized, round-tripped, and passed the stated structural checks, but the claimed runtime behavior has not been established for that arrangement.
Observed in vanillaThe 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.
ExperimentalThe 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:

  1. Which resource owns the missing definition or behavior?
  2. 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 artifactOwnsInstalled or consumed byDo not confuse it with
source\archive\Complete CR2W resources and archived non-CR2W payloads at their intended depot pathsWolvenKit packing/deployment produces a game .archiveLoose ArchiveXL configuration
source\resources\Loose framework configuration such as <project>.archive.xlDeployed beside the archive to the framework’s expected game pathA CR2W resource inside the archive
source\raw\WolvenKit CR2W-JSON review artifacts for mod-owned resourcesDocumentation review, round trips, diagrams, and semantic comparisonRuntime payloads or the beginner authoring format
*.cpmodprojWolvenKit project metadata and source-root configurationWolvenKitA depot resource or registration file
*.archivePacked runtime payload containerCyberpunk 2077’s resource loaderProof that ArchiveXL saw a loose registration file
*.archive.xlArchiveXL merge and registration instructionsArchiveXLThe packed archive or a native soft reference
Lab ZIPDownloadable checkpoint assembled from the projectReader extractionAn 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

ResourceRoot or decisive native typeOwnsBecomes reachable throughCoverage
*.questphasequestQuestPhaseResourceQuest graph, phase interface, condition scheduling, fact/journal operations, scene and child handoff, prefab declarationsA root uses ArchiveXL quest.phases; an external child uses the parent’s soft phaseResource1–5
*.journalgameJournalResourceThe contributed journal-entry tree: quests, phases, objectives, map pins, messages, files, and related entriesArchiveXL journal merge1–5
*.scenescnSceneResourceActors, screenplay items, choices, timed events, ordinary or rewindable graph flow, entry/exit names, interruption policy, and animation referencesA questSceneNodeDefinition.sceneFile soft reference; it is not a quest-root registrationLab 5 plus advanced scenes
*.sceneridscnRidResourceRecorded actor/camera tags, channel serials, animation buffers, and RID-local allocationA scene scnRidResourceHandler, then scene-local RID reference arrays and timed eventsAdvanced 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 .scene owns its named contact_done exit; 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 contentResource typeOwns the decisive lookupRegistration or reference
Journal and onscreen UIJsonResource containing localizationPersistenceOnScreenEntriesText entries keyed by textual secondaryKeyArchiveXL localization.onscreens.<locale>
Spoken subtitles: mapJsonResource containing localizationPersistenceSubtitleMapMap entries whose subtitleFile points to subtitle-entry resourcesArchiveXL localization.subtitles.<locale>
Spoken subtitles: entriesJsonResource containing localizationPersistenceSubtitleEntriesSubtitle text keyed by unsigned stringIdReached through the registered subtitle map; not registered in its place
Spoken voice: mapJsonResource containing locVoiceoverMaplocVoLineEntry rows joining the same stringId to female/male audio pathsArchiveXL localization.vomaps.<locale>
Spoken voice: audio*.wemCooked audio bytesArchived at the femaleResPath / maleResPath named by the VO map
Scene choice textEmbedded scnlocLocStoreEmbedded inside *.sceneDescriptor/payload tables joined from screenplay choice RUIDsNo 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

ResourceRoot or decisive native typeOwnsBecomes reachable throughEvidence boundary
*.streamingblockworldStreamingBlockSector descriptors, sector paths, streaming boxes, categories, levels, and quest-prefab rootsArchiveXL streaming.blocksLab 3–5 shapes are Structurally validated
*.streamingsectorworldStreamingSectorConcrete world nodes, placement/cooked metadata, registered NodeRefs, and references to inplace contentworldStreamingSectorDescriptor.data in a registered blockLab 3–5 compact shapes are Structurally validated
*.streamingsector_inplaceworldStreamingSectorInplaceContentAn inplaceResources[] collection with corresponding embedded CR2W bodies represented separately in the serialized containerA sector’s local or external inplace-resource relationshipObserved in vanilla here; not supplied by Labs 1–5
Base *.streamingworldworldStreamingWorldReferences compiled blocks and broader global world resourcesGame-owned world rootObserved 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.

OwnerNative type or resourceResponsibilityDiscovery path
Registry nodeworldCommunityRegistryNode in an AlwaysLoaded sectorCommunity template, initial entry state, persistent AI-spot data, and the registry side of the community identity joinConcrete sector node plus placement/identity records
Compiled areaworldCompiledCommunityAreaNode_Streamable in a Quest sectorStreamed source placement and entry/phase/time-period mirror to AI-spot IDsConcrete sector node plus placement/identity records
Community entrycommunitySpawnEntry inside communityCommunityTemplateDataEntry CName, character TweakDB record, quantity, and spawn phasesEmbedded in the Lab 5 registry item
Spawn phasecommunitySpawnPhase and communityPhaseTimePeriodPhase, appearance, period, sequencing, and spot referencesEmbedded beneath the community entry
AI spotworldAISpotNode containing AIActionSpotPlaced activity and soft workspot resource pathQuest sector placement and NodeRef registration
Standalone community*.community rooted at communityCommunityTemplateReusable community template data in the cited vanilla examplesObserved in vanilla comparison source; not a Lab 5 dependency
Character recordTweakDBID such as Character.jpn_tyger_claws_biker2_ranged2_copperhead_waGameplay record that leads onward to a character/template dependency chainTweakDB; it is not a depot path
Entity template*.ent, commonly rooted at entEntityTemplateTemplate-specific components, slots, appearances, interactions, and other entity compositionReferenced from records or world entity nodes; advanced authoring is bounded by the compatible mod-owned-shell requirement
Appearance resource*.app rooted at appearanceAppearanceResourceInternal appearance definitions and their component graphsAn entity template’s exposed appearance mapping; advanced custom behavior remains Experimental
Workspot*.workspotReferenced activity/animation behaviorworldAISpotNode.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:

SurfaceOwnerBoundary
Placed entity or deviceworldEntityNode or worldDeviceNodeRefers to an entity template and may carry node-local instanceData
Node-local controller/component stateentEntityInstanceData leading to a RedPackageDifferent from sector inplace content
Global device tables*.devices and *.psrepContext-dependent; absence from one focused vanilla flow is not a universal no-registration rule
Persistent device identitySave plus world identity/NodeRefA 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?”

TargetArchiveXL registration?Native reference that reaches non-root dependencies
Registered root questphaseYes: quest.phasesParent is base\quest\cyberpunk2077.quest
External child questphaseNo independent root entryParent questPhaseNodeDefinition.phaseResource
Journal contributionYes: journalQuest nodes then use typed gameJournalPath values
Onscreen localizationYes: localization.onscreens.<locale>Journal/UI LocalizationString.value joins to secondaryKey
Subtitle mapYes: localization.subtitles.<locale>Map entry subtitleFile reaches subtitle entries
Subtitle entriesNo direct registration in this routeRegistered subtitle map
VO mapYes: localization.vomaps.<locale>Its line entries reach WEM resource paths
WEMNo direct registrationVO-map femaleResPath / maleResPath
Streaming blockYes: streaming.blocksIts descriptors reach sectors
Streaming sectorNo separate block registrationDescriptor data resource reference
SceneNo quest-root registrationquestSceneNodeDefinition.sceneFile
Scene RID or animation-set leafNo independent quest-root registrationScene RID handlers and resouresReferences arrays
Mod-owned character recordNot through ArchiveXL; use the locally pinned TweakXL 1.11.3 routeRecord entityTemplatePath reaches the archived .ent
Mod-owned entity/appearance resourceArchived at a unique depot path; no root mergeCharacter record reaches .ent; entity appearance mapping reaches .app
Vanilla workspot or animation setNo new registration in the taught routeAI spot or scene resource-reference path

Custom visual effects

Resource or recordOwnsReached through
TweakDB status recordDuration and VFX/SFX requestsItem, quickhack, or other gameplay action
StatusEffectFX recordRequested effect CNameStatus record’s VFX array
entEffectSpawnerComponent / entEffectDescEffect-name lookup, timeline path, event placementsComponent on the target entity; add with an ArchiveXL resource.patch when needed
.effect / worldEffectParticle/sound tracks, loop range, outroDescriptor’s effect reference
.particle / CParticleSystemEmitters, drawer, authored and compiled simulationeffectTrackItemParticles.particleSystem
Local CMaterialInstanceTexture references, tint, opacity, blendingEmitter’s localMaterialInstance
.xbm texturesColor and mask pixelsMaterial’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 resourcesState that can remain in the save
Fact names and graph writers/readersCurrent fact integer values
Journal entries and typed pathsActive, inactive, succeeded, failed, visited, and tracking state
Questphase nodes and edgesActive phase work and checkpoints
Scene graph, actors, and exitsActive or completed scene state
Community registry, area, and spotsActivation and actor lifecycle state
Device template and instance dataPersistent 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:

  1. the graph-local numeric id and containing .questphase;
  2. the concrete RED node type;
  3. the decisive typed payload or external reference;
  4. 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 typeJobDecisive property or payloadFocused socketsFirst complete use
Input / questInputNodeDefinitionExpose one phase entrysocketName, such as In1internal Out; CutDestinationLab 1
Output / questOutputNodeDefinitionExpose a phase result and optionally terminatesocketName, such as Out1; output typeinternal In; CutDestinationLab 1
Condition / questConditionNodeDefinitionEvaluate a predicate on entry and branch immediatelycondition handleIn, True, False, CutDestinationLab 1
PauseCondition / questPauseConditionNodeDefinitionHold one active route until a predicate is fulfilledcondition handleIn, Out, CutDestinationLab 1
Journal / questJournalNodeDefinitionRequest a state change for one typed journal pathtype handle, including path and presentation propertiesActive, Inactive, Succeeded, Failed inputs; Out; CutDestinationLab 1
FactsDBManager / questFactsDBManagerNodeDefinitionWrite a factquestSetVar_NodeType, including fact name, value, and exact/add policyIn, Out, CutDestinationLab 1
MappinManager / questMappinManagerNodeDefinitionRequest active/inactive state for a journal quest pinTyped pin path; disablePreviousMappinsActive, Inactive, Out, CutDestinationLab 3
Phase / questPhaseNodeDefinitionInvoke an inline or external child phaseExternal form: phaseResource, null phaseGraph, and phaseInstancePrefabsParent/child interface names such as In1, Out1; CutDestinationLab 4
SpawnManager / questSpawnManagerNodeDefinitionIssue community/spawner actionsactions[]; Lab 5 uses questCommunityTemplate_NodeTypeIn, Out, CutDestinationLab 5
Checkpoint / questCheckpointNodeDefinitionEstablish an explicit quest checkpoint in the supplied lifecycleCheckpoint flags and containing graph positionIn, Out, CutDestinationLab 5
Scene / questSceneNodeDefinitionStart an archived scene at a world marker and receive named resultssceneFile, sceneLocation, interruption fieldsNamed entry inputs and exit outputs; interruption/return outputs; CutDestinationLab 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:

InputRequested journal state
ActiveActivate the target entry
InactiveInactivate it
SucceededMark it succeeded
FailedMark 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 typeStructural roleSocket/property shapeEvidence boundary
questLogicalAndNodeDefinitionJoin-shaped convergenceinputSocketCount, outputSocketCount, numbered ordinary socketsObserved in vanilla: 2–11 inputs and one output in the retained corpus; arrival memory and save restoration are not established by the type name
questLogicalXorNodeDefinitionRace-shaped convergenceinputSocketCount, outputSocketCount, numbered ordinary socketsLab 2’s exact two-input/one-output shape is Structurally validated; automatic loser cancellation is not implied
questLogicalHubNodeDefinitionGeneral structural routingCounts plus ordinary input/output socketsObserved in vanilla: split, merge, and 1→1 shapes all occur; “fan-out node” is not a complete definition
questSwitchNodeDefinitionMap conditions to case socketsconditions[], each socketId; Case<socketId> and Otherwise outputsObserved in vanilla for one retained switch; ordering and Otherwise runtime policy remain Experimental
questCutControlNodeDefinitionEmit an explicit cut toward named targetsordinary In/Out plus CutSourceCutSource→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 typeJobDecisive payloadEvidence route
questRewardManagerNodeDefinitionGrant one or more reward recordsquestGiveReward_NodeType.rewards[]The focused arrangement is covered in Rewards and completion; it is not a Labs 1–5 dependency
questPuppetAIManagerNodeDefinitionRequest a puppet AI tier or operationTarget entity reference and operation-specific fieldsLab 5 wraps one Cinematic-tier request inside scnQuestNode; see Entry, exit, and quest handoff
questMiscAICommandNodeAssign or clear a temporary scripted AI roleTyped command params, target actor, follow/path/role dataAI roles, behavior, and workspots
questUseWorkspotNodeDefinitionPut a selected actor into a world or scene-local workspot contractActor reference plus versioned workspot params/instance IDAnimation events and workspots
questInteractiveObjectManagerNodeDefinitionIssue a typed operation to an interactive object/devicequestDeviceManager_NodeType, controller/action names, target and action propertiesAdvanced 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 typeLocal meaningCommon mistake
In / OutOrdinary internal entry and continuationTreating Out as proof that an external system visibly completed
In1 / Out1First named phase interface or numbered logical socket, depending on node typeAssuming the text has one global meaning or that every phase exposes both
True / FalseResults of an immediate Condition nodeExpecting the False route to wait and later become True
Active, Inactive, Succeeded, FailedState-operation inputs on journal-like manager nodesLooking only at the node payload and ignoring which input the edge reaches
Case<n> / OtherwiseSwitch outputs mapped from condition itemsAssuming case evaluation order from array order
CutSourceExplicit cancellation source on a Cut ControlWiring it as ordinary success flow
CutDestinationTarget for a cut edgeAssuming an unwired destination provides automatic cleanup
Scene entry name, such as startInput on questSceneNodeDefinition matching a scene entryPointUsing the scene Start node’s numeric ID as the caller interface
Scene exit name, such as contact_doneOutput on questSceneNodeDefinition matching a scene exitPointInventing an end output because the scene contains an End node
<scenario> INT / <scenario> RETQuest-scene outputs associated with scene interruption and return policyTreating 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:

SocketTypeConnection
CutDestinationCutDestinationUnconnected
startInputFrom the child route after community readiness and broad setup
contact_doneOutputTo the meet-success and cleanup route
Default INTOutputUnconnected
Default RETOutputUnconnected

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 typeJobBoundary data
scnStartNodeReceive the published start entry and fan one 0/0 output to two destinationsListed in sceneGraph.startNodes; targeted by entryPoints[].nodeId
scnSectionNodeOwn actor behavior, timed line events, duration, and normal/cancel outputsNormal 0/0 reaches End; cancel 1/0 is unconnected in the fixture
scnQuestNodeWrap a scene-local quest node; First Contact wraps questPuppetAIManagerNodeDefinitionInput/output mappings connect scene stamps to the wrapped quest sockets
scnEndNodeTerminate the normal scene routeListed in sceneGraph.endNodes; targeted by the named contact_done exit
scnChoiceNodePresent scene-local screenplay options and route the selected branchOption-to-screenplay IDs, choice conditions, output ordinals, and later named exits are separate joins
scnRewindableSectionNodeOwn a seekable/rewindable timed-event collectionPlayback 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:

SourceOutput stampDestinationInput stamp
Start 1name 0, ordinal 0Section 2name 0, ordinal 0
Start 1name 0, ordinal 0PuppetAI wrapper 4name 0, ordinal 1
Section 2 normalname 0, ordinal 0End 3name 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:

  1. Name the containing questphase or scene graph as well as the node ID.
  2. Confirm the concrete RED type and every non-null typed payload handle.
  3. Trace each edge by source socket and destination socket.
  4. Distinguish one socket with several connections from several sockets.
  5. Distinguish a quest socket from a scene socket stamp.
  6. Confirm every phase input/output and scene entry/exit name on both sides.
  7. Inventory parallel waits, delays, scenes, community operations, and cut targets before permitting termination.
  8. 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 nodeQuestion answered on entryFocused 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

FamilyWrapperConcrete payload or direct fieldsDecisive valuesBook evidence
Fact comparisonquestFactsDBConditionquestVarComparison_ConditionTypefactName, signed value, comparisonType259 retained instances are Observed in vanilla; exact Lab 1–5 fact predicates are Structurally validated
TriggerquestTriggerConditionDirect type, triggerAreaRef, activatorRef, isPlayerActivatorIsInside, IsOutside, or another specifically evidenced predicate; exact NodeRef and activator214 retained instances are Observed in vanilla; exact Lab 3–5 player gates are Structurally validated
DistancequestDistanceConditionquestDistanceComparison_ConditionType containing operand handlesquestObjectDistance, questValueDistance, comparator, both object identities55 retained instances are Observed in vanilla; no Lab 1–5 distance gate
Journal lifecyclequestJournalConditionquestJournalEntryState_ConditionTypeTyped path, engine state, payload-specific inverted66 retained instances are Observed in vanilla
Journal user statequestJournalConditionquestJournalEntry_ConditionTypeTyped path and user-facing state21 retained instances are Observed in vanilla
Journal visitedquestJournalConditionquestJournalEntryVisited_ConditionTypeTyped path and visitedOne retained instance is Observed in vanilla
Realtime delayquestTimeConditionquestRealtimeDelay_ConditionTypehours, minutes, seconds, native spelling miliseconds146 retained instances are Observed in vanilla; exact Lab 1, 2, and 4 delays are Structurally validated
Game-time delayquestTimeConditionquestGameTimeDelay_ConditionTypedays, hours, minutes, seconds19 retained instances are Observed in vanilla
Character spawnedquestCharacterConditionquestCharacterSpawned_ConditionTypeobjectRef, comparison type/count, entireCommunity73 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:

comparisonTypePredicateRetained instances
Equalfact equals authored value55
NotEqualfact does not equal authored value2
Greaterfact is greater than authored value138
GreaterOrEqualfact is at least authored value55
Lessfact is less than authored value9
LessOrEqualfact is at most authored value0

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.typeRetained instances
IsInside153
IsOutside56
Entered5

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 questTimeConditionRetained instancesCurrent depth
questRealtimeDelay_ConditionType146Property recipe and Structurally validated lab examples
questGameTimeDelay_ConditionType19Property recipe plus vanilla observations
questTimePeriod_ConditionType2Inventory only
questTickDelay_ConditionType1Inventory 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 operationRetained instancesSafe current statement
AND72Every represented child predicate is required by the authored tree; exact evaluation order and short-circuit policy are not inferred
OR14The tree represents alternatives but does not report which child supplied fulfillment
XOR1The 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 payloadRetained instancesExternal owner to identify before authoring
questDevice_ConditionType44Placed device identity, template/controller state, and the producer of the observed device state
questInventory_ConditionType11Item/record identity, inventory owner, quantity or state, and the operation that changes it
questScan_ConditionType3Scannable target identity and scan event/state owner
questDestruction_ConditionType2Target 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 payloadRetained instances
questCharacterSpawned_ConditionType73
questCharacterMount_ConditionType67
questCharacterKilled_ConditionType37
questCharacterStatusEffect_CondtionType29
questCharacterState_ConditionType18
questCharacterCombat_ConditionType11
questCharacterHealth_ConditionType4
questCharacterHit_ConditionType4
questCharacterWorkspot_ConditionType4
questCharacterAttack_ConditionType1
questCharacterGender_CondtionType1
questCharacterQuickHacked_ConditionType1

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

WrapperConcrete payloadRetained instancesBoundary
questSceneConditionquestSceneNode_ConditionType31Scene state consumer; it does not launch the scene
questContentConditionquestContentLock_ConditionType10Content-lock predicate inventory
questContentConditionquestContentToken_ConditionType5Content-token predicate inventory
questSystemConditionquestPhoneMuted_ConditionType26Phone-muted predicate inventory
questSystemConditionquestCameraFocus_ConditionType24Camera-focus predicate inventory
questSystemConditionquestPhone_ConditionType4Phone-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

WrapperConcrete payloadRetained instancesBoundary
questVehicleConditionquestVehicleSpeed_ConditionType3Identify the exact vehicle, load/spawn owner, and speed comparison
questVehicleConditionquestVehicleTrunk_ConditionType2Identify vehicle/trunk state and the producer of that state
questVehicleConditionquestVehicleWater_ConditionType1Inventory only
questSpawnerConditionquestSpawnerReady_ConditionType7Identify 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.

FamiliesDepot path
Facts, logical trees, triggers, time, journal, contentbase\open_world\minor_activities\watson\northside\ma_wat_nid_15\ma_wat_nid_15_phase.questphase
Game-time/realtime, journal, content token, phonebase\quest\side_quests\sq011\phases\sq011_concert.questphase
Inventory, scan, scene, character, vehicle trunkbase\open_world\street_stories\heywood\vista_del_rey\sts_hey_rey_09\phases\sts_hey_rey_09_openworld.questphase
Device, killed, spawned, workspot, quickhackbase\open_world\street_stories\watson\little_china\sts_wat_lch_01\phase\sts_wat_lch_01_combat.questphase
Destruction, device, combat, vehicle speedbase\open_world\street_stories\watson\northside_industrial_district\sts_wat_nid_03\phases\sts_wat_nid_03_openworld.questphase
Spawner ready, distance, character mountbase\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:

  1. the exact immediate or waiting graph node and socket route;
  2. the wrapper, nested concrete payload, and every decisive field;
  3. ownership and resolution for every external identifier;
  4. the system or graph operation that produces the observed state;
  5. a mod-owned WolvenKit 8.19.0 round trip with non-null compatible handles;
  6. already-true, normal-transition, false/failure, save/load, stream-loss, cancellation, completion, and re-entry cases as applicable;
  7. 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

DomainNative type or serialized shapeScope / ownerExampleNever substitute
Depot addressResourcePath or a registration path valueGame virtual depotmod\cqa\cqa005\scenes\cqa005_first_contact.sceneWindows path, NodeRef, journal path
CR2W object identityHandleId / HandleRefIdOne serialized CR2W object graph"HandleId": "15"Graph node ID, scene ID, world identity
Quest graph nodeinteger node idOne questGraphDefinitionLab 1 FactsDB node 15HandleId, scene node ID
Quest socket/interface nameCNameOne node or parent/child interfaceIn, Out, In1, Out1, contact_doneNumeric node ID or ordinal
Scene graph nodescnNodeIdOne scnSceneGraphFirst Contact Section 2Quest node ID or performer ID
Scene socket stampname plus ordinalOne scene-node socket mappingoutput 0/0, input 0/1Quest questSocketDefinition
World referenceNodeRefRegistered world / quest-prefab hierarchy#cqa005_com_contactCR2W handle, ResourcePath, TweakDBID
Gameplay recordTweakDBIDTweakDBCharacter.jpn_tyger_claws_biker2_ranged2_copperhead_wa.ent path, NodeRef
Name tokenCNameField-specific RED name domaincommunity entry contactFact, localization key, ResourcePath
FactfactName string with signed integer valueFacts database and savecqa005_completedCName, journal ID, Boolean field
Journal lookupgameJournalPathMerged journal treequests/minor_quest/cqa005/...Depot path, localization key
Journal componententry idParent entry’s child collectioncqa005_01_obj_meetfileEntryIndex, graph ID
UI localizationtextual secondary keyMerged onscreen localization tablecqa_cqa001_titleSpoken-line RUID
Spoken/choice localizationscnlocLocstringId.ruidScene localization lookup9638591835734011695Screenplay item ID, event ID
Scene actorscnActorIdOne scenecontact 0, V 1Performer ID, community ID
Scene performer symbolscnPerformerIdScene debug-symbol mappingcontact 1, V 257Actor ID
Screenplay itemscnscreenplayItemIdOne screenplay storeFirst Contact line 1Locstring or event ID
Timed scene eventscnSceneEventIdOne scene event collection8646165628675208917Locstring or screenplay item ID
Lipsync resource slotscnLipsyncAnimSetSRRefIdIndex into one scene’s lipsync arrayslot 0Actor 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:

UseOwnerMeaning
Root quest.phases[].pathArchiveXLAttach a mod-owned root beneath the game quest root
phaseResource.DepotPathquestPhaseNodeDefinitionResolve an archived external child
sceneFile.DepotPathquestSceneNodeDefinitionResolve an archived scene
Descriptor data.DepotPathworldStreamingSectorDescriptorResolve 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 HandleRefId must 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

ValueTypeResolves
#cqa005_com_contactNodeRefA world object beneath an available prefab root
mod\cqa\cqa005\scenes\cqa005_first_contact.sceneResourcePathA CR2W resource in the depot
Character.jpn_tyger_claws_biker2_ranged2_copperhead_waTweakDBIDA 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:

DomainCanonical Lab 5 valueDerived from
Community/source identity5948510988927765319Full community NodeRef
Registry-node identity6908684691797323855Full community NodeRef with _registry appended to the final community name
AI-spot global identity15950783814303760596Full 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:

FieldDomain
realPathSlash-separated journal-entry IDs
classNameCName for the intended leaf entry type
fileEntryIndexZero-based component position of the containing gameJournalFileEntry
editorPathEditor-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

ContentIdentifierOwner
Journal/UI textTextual LocalizationString.value matching onscreen secondaryKeyRegistered onscreen localization resource
Spoken scene lineUnsigned scnlocLocstringId.ruid matching subtitle and VO stringIdExternal subtitle-map/entries and VO-map/WEM branches
Scene choiceUnsigned locstring RUID reached through a screenplay optionScene’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 domainValuesIdentifies
scnActorId0, 1Contact and V actor definitions
scnPerformerId1, 257Debug performer-symbol rows
scnNodeId1, 2, 3, 4Start, Section, End, and PuppetAI wrapper
scnscreenplayItemId1The screenplay line and the event-to-line join
scnSceneEventId8646165628675208917The timed line event instance
scnlocLocstringId9638591835734011695External subtitle and voice lookup
scnLipsyncAnimSetSRRefId0Slot in resouresReferences.lipsyncAnimSets
scnInterruptionScenarioId0The 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:

RoleActor IDPerformer ID
Contact01
V1257

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:

FieldRole
scnChoiceNodeOption.screenplayOptionIdJoin from graph option to screenplay option
scnscreenplayChoiceOption.itemIdScreenplay option identity
scnscreenplayChoiceOption.locstringIdChoice-text lookup identity
Descriptor variantId.ruidJoin to the matching payload variant
Descriptor vpeIndexZero-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:

  1. Record the native type and full property path.
  2. Record the owner and scope: CR2W graph, quest graph, scene, journal tree, world hierarchy, TweakDB, localization table, or save.
  3. Record the canonical source string for derived world IDs.
  4. Verify every join on both sides in the mod-owned resources.
  5. Reject null handles, zero values where identity is required, out-of-range indices, and unintended collisions.
  6. Reopen and round-trip the resources before comparing semantics.
  7. 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.

RecordValue
Practical baselineCyberpunk 2077 2.31a, WolvenKit 8.19.0, ArchiveXL 1.27.0, RED4ext 1.30.0, redscript 0.5.31
Lab 1–5 path resourcesStructurally validated
In-game state transitionsVerify 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 fieldRED value kindWhat to enter
realPathStringSlash-separated entry id values, excluding the anonymous gameJournalRootFolderEntry
classNameCNameExact RED class of the intended target in tutorial-owned trees
fileEntryIndexIntegerZero-based realPath component containing the nearest gameJournalFileEntry
editorPathStringEmpty 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 familyTypical containing file entryExample index
quests/minor_quest/<quest>/...gameJournalQuest at component 22
contacts/<contact>/...gameJournalContact at component 11
points_of_interest/minor_quests/...gameJournalPointOfInterestGroup at component 11
onscreens/emails/quests/minor_quest/<quest>/files/...gameJournalFileGroup at component 55
onscreens/emails/quests/minor_quest/<quest>/messages/...gameJournalOnscreenGroup at component 55

Calculate the value from the actual contributed tree. A different folder layout can move the containing file entry.

Common target classes

TargetclassName
QuestgameJournalQuest
Quest phasegameJournalQuestPhase
Quest objectivegameJournalQuestObjective
Objective descriptiongameJournalQuestDescription
Quest mappingameJournalQuestMapPin
ContactgameJournalContact
Phone messagegameJournalPhoneMessage
Phone choice groupgameJournalPhoneChoiceGroup
Phone choicegameJournalPhoneChoiceEntry

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.

LabEntryrealPath
First SignalQuestquests/minor_quest/cqa001
First SignalPhasequests/minor_quest/cqa001/cqa001_01
First SignalWait objectivequests/minor_quest/cqa001/cqa001_01/cqa001_01_obj_wait
Signal RaceQuestquests/minor_quest/cqa002
Signal RacePhasequests/minor_quest/cqa002/cqa002_01
Signal RaceWait objectivequests/minor_quest/cqa002/cqa002_01/cqa002_01_obj_wait
Signal RaceStable objectivequests/minor_quest/cqa002/cqa002_01/cqa002_01_obj_stable
Boundary CheckQuestquests/minor_quest/cqa003
Boundary CheckPhasequests/minor_quest/cqa003/cqa003_01
Boundary CheckReach objectivequests/minor_quest/cqa003/cqa003_01/cqa003_01_obj_reach
Boundary CheckQuest mappinquests/minor_quest/cqa003/cqa003_01/cqa003_01_obj_reach/cqa003_01_qmp_checkpoint
Boundary CheckLeave objectivequests/minor_quest/cqa003/cqa003_01/cqa003_01_obj_leave
Handoff PointQuestquests/minor_quest/cqa004
Handoff PointPhasequests/minor_quest/cqa004/cqa004_01
Handoff PointReach objectivequests/minor_quest/cqa004/cqa004_01/cqa004_01_obj_reach
Handoff PointQuest mappinquests/minor_quest/cqa004/cqa004_01/cqa004_01_obj_reach/cqa004_01_qmp_handoff
Handoff PointLeave objectivequests/minor_quest/cqa004/cqa004_01/cqa004_01_obj_leave
Handoff PointConfirmation objectivequests/minor_quest/cqa004/cqa004_01/cqa004_01_obj_confirm
First ContactQuestquests/minor_quest/cqa005
First ContactPhasequests/minor_quest/cqa005/cqa005_01
First ContactMeet objectivequests/minor_quest/cqa005/cqa005_01/cqa005_01_obj_meet
First ContactQuest mappinquests/minor_quest/cqa005/cqa005_01/cqa005_01_obj_meet/cqa005_01_qmp_contact
First ContactLeave objectivequests/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:

  1. The cooked .journal exists beneath source\archive at the intended depot path.
  2. The loose .archive.xl registers that exact depot path.
  3. The journal’s entry id chain produces realPath exactly, including case and separators.
  4. className matches the mod-owned target type.
  5. fileEntryIndex identifies the nearest containing file entry.
  6. The localization key on the target resolves through the separate onscreen lookup.
  7. 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.

RecordValue
Practical baselineCyberpunk 2077 2.31a, WolvenKit 8.19.0, ArchiveXL 1.27.0, RED4ext 1.30.0, redscript 0.5.31
Lab 1–5 localization resourcesStructurally validated
In-game presentationVerify 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 screenSource identityRuntime ownerArchiveXL registration
Quest title, phase, objective, description, journal mappin, phone UI, file, shard, or onscreen textTextual LocalizationString.valuelocalizationPersistenceOnScreenEntrieslocalization.onscreens.<locale>
Spoken scene subtitle and VOUnsigned numeric scnlocLocstringId.ruidSubtitle map -> subtitle entries, plus VO map -> WEMlocalization.subtitles.<locale> and localization.vomaps.<locale>
Scene choice labelUnsigned numeric scnlocLocstringId.ruid reached through a screenplay optionScene-embedded scnlocLocStoreEmbeddedNone; 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 propertyContract used by Labs 1–5
Root data typelocalizationPersistenceOnScreenEntries
Entry typelocalizationPersistenceOnScreenEntry
primaryKey0 in the mod-added textual-key shape
secondaryKeyGlobally unique text equal to LocalizationString.value
femaleVariantAuthored text in the supplied labs
maleVariantOptional 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 itemDecisive field
scnscreenplayDialogLinelocstringId.ruid
localizationPersistenceSubtitleMapEntrysubtitleFile soft resource reference
localizationPersistenceSubtitleEntryMatching stringId, femaleVariant, maleVariant
locVoLineEntryMatching stringId, femaleResPath, maleResPath
WEMCooked 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:

ItemValue
Authored line keycqa005_contact_line_0001
Unsigned line RUID9638591835734011695
Subtitle mapmod\cqa\cqa005\localization\en-us\subtitles\cqa005_subtitles_map.json
Subtitle entriesmod\cqa\cqa005\localization\en-us\subtitles\cqa005_subtitles.json
VO mapmod\cqa\cqa005\localization\en-us\vo\cqa005_vo.json
WEMmod\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
FieldRule
Choice captionAuthoring/debug CName; not authoritative displayed text
Descriptor identityMatch locale, locstring RUID, signature, and variant
vpeIndexZero-based index into vpEntries[]
variantId.ruidPreserve between descriptor and intended payload
Numeric orderingCompare 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

SymptomInspect first
Journal entry exists but title/objective is blankLocalizationString.value -> onscreen secondaryKey; primaryKey: 0; localization.onscreens.<locale> registration
Only one player variant is correctBoth femaleVariant and maleVariant; test both intended player configurations
Spoken subtitle is absentScene line RUID -> subtitle stringId; subtitle-map registration; map’s subtitleFile path
Subtitle appears but audio is silentSame RUID in VO map; both WEM resource paths; packed WEM; audio and ArchiveXL logs
Choice caption is right but menu text is blank or staleOption ID -> screenplay option -> locStore descriptor -> vpeIndex -> matching variant payload
Only one embedded locale is wrongThat locale’s descriptor block, numeric ordering, payload indices, and variant identities
A rebuilt string behaves like an older candidateInstalled 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.

RecordValue
ArchiveXL syntax baseline1.27.0
Complete practical baselineCyberpunk 2077 2.31a, WolvenKit 8.19.0, ArchiveXL 1.27.0, RED4ext 1.30.0, redscript 0.5.31
Lab 1–5 registration filesStructurally validated
In-game registrationVerify 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

ConcernRegister thisLeave these as packed dependencies
Quest compositionOne intended root questphase under quest.phasesExternal child questphases reached through phaseResource
JournalEach contributed .journal under journalJournal entries and handles inside that resource
Journal/UI textCooked onscreen localization resource under localization.onscreens.<locale>Journal resources that carry its textual keys
Spoken subtitlesSubtitle map under localization.subtitles.<locale>Subtitle-entry resource reached by the map
Spoken VOVO map under localization.vomaps.<locale>WEM files reached by map entries
World streamingOne .streamingblock under streaming.blocksSector files reached by block descriptors, plus resources inside those sectors
SceneNothing 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
KeyMeaning
pathPacked depot path of the mod-owned composition root
parentGame-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

  1. Close the game and framework processes before replacing either payload.
  2. Inventory installed candidates; do not leave a start checkpoint, completed checkpoint, or older archive with the same depot paths enabled.
  3. Confirm every registered value is an exact cooked depot path and every referenced dependency is present in the archive.
  4. Confirm quest.phases contains only the intended composition root or roots.
  5. Confirm subtitle registration names the map, streaming registration names the block, and neither indirect leaf is added as a root.
  6. Pack and list the archive’s depot paths.
  7. Install the .archive and matching .archive.xl; keep the pair together.
  8. Start the game and inspect RED4ext and ArchiveXL logs for load, registration, merge, and missing-resource errors.
  9. Run from a save created before the relevant quest, journal, scene, community, and device state existed.
  10. 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

SymptomInspect first
Root quest never startsYAML nesting, root depot path, parent, installed .archive.xl, ArchiveXL log, and starting save
Parent starts but child never resolvesPacked child path and parent phaseResource; do not add a second root registration as a shortcut
Journal transition node runs but no entry appearsJournal registration, typed journal path, and save-backed state
Journal entry exists but text is blankOnscreen registration and localization key join
Subtitle is absentRegistered subtitle map, its subtitleFile reference, and line RUID
Subtitle works but audio is silentRegistered VO map, WEM path and payload, line RUID, and audio logs
World content never appearsRegistered block, descriptor-to-sector paths, bounds/category, and quest-prefab scope
Scene path cannot loadPacked .scene and quest node soft resource path; no separate Lab 5 scene registration exists
Old behavior survives a rebuildDuplicate 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.

LabTest guide
First SignalInstall, test, and reset
Signal RaceTest both routes
Boundary CheckTest Boundary Check
Handoff PointTest Handoff Point
First ContactTest 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.

RecordValue
Practical comparison targetCyberpunk 2077 2.31a, WolvenKit 8.19.0
Evidence classObserved in vanilla, limited to the question named for each path

Core roots, journal, and localization

Depot pathInspect it for
base\quest\cyberpunk2077.questGame-owned parent used by ArchiveXL root-questphase registration; cite it, do not add an extracted copy to the mod
base\journal\descriptor.journaldescStandard soft descriptor reference used by contributed journal roots
base\journal\cooked_journal.journalFocused journal entry families, typed paths, file-entry indices, mappins, contacts, messages, files, and saved presentation targets
base\localization\en-us\onscreens\onscreens.jsonFocused 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 pathFocused question
base\quest\side_quests\sq021\phases\sq021_randys_room.questphaseHow 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.questphasePrefab 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.questphaseFacts, 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.questphaseA 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.questphaseA 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.questphaseA 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.questphaseGame-time and real-time payloads plus delayed contact orchestration
base\quest\side_quests\sq011\phases\sq011_follow_up.questphaseMulti-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.questphaseParallel 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.questphaseCombat-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.questphaseParent prefab scope and external-child composition
base\open_world\street_stories\watson\little_china\sts_wat_lch_01\phase\sts_wat_lch_01_openworld.questphaseDevice interaction, upload, and scan condition shapes
base\open_world\street_stories\watson\little_china\sts_wat_lch_01\phase\sts_wat_lch_01_combat.questphaseDevice, 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.questphaseA 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.questphaseEmpty 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.questphaseEscort 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.questphaseInventory, 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.questphaseSpawner readiness, distance, mount, and defend-target comparison
base\open_world\street_stories\watson\kabuki\sts_wat_kab_05\phases\sts_wat_kab_05_openworld.questphaseJournal completion/reward context and native drop-point reservation comparison
base\quest\side_quests\sq004\phases\sq004_02_drive.questphasePlayer/contact mount conditions, vehicle assignment, seat roles, and destination-driving comparison
base\quest\side_quests\sq031\phases\sq031_rogue.questphaseSpawned readiness, puppet-AI tier work, and vehicle-forbidden-trigger context
base\quest\side_quests\sq031\phases\sq031_porsche.questphasePlayer-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.questphasePlayer-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.questphaseComposite chase staging: vehicle-seat assignment, AI roles, and route/lost-state facts
base\quest\side_quests\sq024\phases\sq024_05_the_big_race.questphaseRace start/stop/recovery, course splines, competitor parameters, checkpoints, and restriction lifecycle
base\quest\side_quests\sq004\phases\sq004_03_raffen_shiv_camp.questphaseRelease/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.questphaseDevice release plus named-target readiness comparison
base\open_world\street_stories\santo_domingo\arroyo\sts_std_arr_05\phases\sts_std_arr_05_openworld.questphasePlant-item device action/condition and inventory-consumption comparison
base\open_world\phases\cyberpsychos\open_world_cyberpsychos.questphaseShared 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 pathFocused question
base\quest\minor_quests\mq003\mq003_orbitals.questphaseWhole-community activation, spawned readiness, and activity-phase invocation
base\quest\minor_quests\mq003\phases\mq003_homeless.questphaseNamed community phase changes and later deactivation
base\quest\minor_quests\mq003\scenes\mq003_01_homeless.sceneCommunity actor acquisition, entry/exit shape, performers, screenplay, events, and embedded localization ordering
base\quest\minor_quests\mq003\scenes\mq003_03_orbital_pod.sceneComparable scene resource, actor, screenplay, event, and locStore structures
base\quest\minor_quests\mq007\scenes\mq007_01_gun_found.sceneComparable scene graph and embedded locStore ordering
base\quest\minor_quests\mq010\scenes\mq010_02_barry_talk.sceneComparable dialogue scene and embedded locStore ordering
base\animations\quest\minor_quests\mq007\anim\body\mq007__talking_gun__male_fpp.animsDirect 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.animsEnglish 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.animsSeparate English V lipsync-set resource and performer-slot/cardinality comparison
base\quest\side_quests\sq011\scenes\sq011_09_nancy_call.sceneHolocall 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.communityGeneric Tyger Claw entries, phases, appearances, time periods, quantities, and AI-spot references
ep1\characters\entities\gang\gang__ep1_tyger_wa.entEntity-template appearance mappings exposed by the focused Character.* record inheritance chain
base\characters\appearances\gang\gang__tyger_wa.appInternal 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.workspotThe stationary cigarette workspot used by the Lab 5 contact
base\workspots\common\ground\generic__stand_ground__guard__02.workspotA 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.workspotA scene-referenced wall-lean workspot used to distinguish workspot acquisition from direct animation events
base\workspots\patrolling\guard_stand.workspotFinite-patrol research only; not a universal guard workspot
base\animations\facial\generic\interactive_scene\generic_facial_lipsync_gestures.animsGeneric 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 pathFocused question
base\quest\side_quests\sq012\scenes\sq012_02a_braindance.sceneRewindable 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.sceneridVersion-5 RID actor/camera tags, body/facial/cyberware channels, serials, cardinalities, and compressed buffers
base\quest\side_quests\sq012\entities\sq012_02a_braindance_bdview.entBD-view entity template selected through a scene prop record and global spawn marker
base\quest\side_quests\sq012\entities\sq012_02a_braindance_bdfog.entBD-fog entity template selected through a separate scene prop record
base\quest\side_quests\sq012\entities\sq012_02a_braindance_bdsetup.entRender-to-texture BD-setup entity template selected through a separate scene prop record
base\quest\main_quests\prologue\q004\scenes\q004_05_bd_yorinobu.sceneLarger 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.questphaseQuest-side setup/teardown decomposition around the Q004 braindance; not a self-contained custom-scene template
engine\scenesystem\camera.entScene-camera entity shape used for a placed RID-camera comparison; it does not supply a candidate’s world placement

World, placement, and devices

Depot pathFocused question
base\worlds\03_night_city\_compiled\default\03_night_city.streamingworldGlobal Night City resource roots and compiled block ownership
base\worlds\03_night_city\_compiled\default\blocks\all.streamingblockSector descriptors, categories, bounds, levels, and quest-prefab roots
base\worlds\03_night_city\_compiled\default\quest_606b61008df2ba6f.streamingsectorFocused Quest-sector nodes, placements, NodeRefs, trigger data, and prefab namespace
base\worlds\03_night_city\_compiled\default\always_loaded_0.streamingsectorAlwaysLoaded references and marker-related content without assuming compact array pairing
base\worlds\03_night_city\_compiled\default\exterior_-18_28_0_0.streamingsectorAmbient terminal placement comparison
base\worlds\03_night_city\_compiled\default\bd21168eed6c6d62.streamingsector_inplaceSeparate inplace payload associated with that focused terminal placement
base\worlds\03_night_city\_compiled\default\exterior_-17_22_0_0.streamingsectorAllen Street fast-travel terminal placement, transform, entity template, and NodeRef chain
base\worlds\03_night_city\_compiled\default\always_loaded_1.streamingsectorAllen Street destination-marker registration and placement
base\gameplay\devices\fast_travel\data_term_1.entEntity template used by the focused fast-travel placement
base\worlds\03_night_city\_compiled\default\exterior_19_-8_0_0.streamingsectorPlaced laptop node data, entity template, instance data, and cooked prefab link
base\worlds\03_night_city\_compiled\default\4fd0915183681e53.streamingsector_inplaceWhat the laptop placement’s separate inplace resource embeds and omits
base\gameplay\devices\drop_points\drop_point.entDrop-point components and local UI/navigation slots
base\gameplay\devices\masters\computers\laptop_1.entBase computer components, controller package, and interaction/workspot ownership used by focused laptop comparisons
base\worlds\03_night_city\_compiled\default\03_night_city.devicesFocused 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 pathInspect it for
base\fx\player\status_effects\blindness\p_blindness.particleScreen particle drawer, emitter modules, local material, and compiled render data
base\fx\player\p_hacking_glitch\p_hacking_glitch_low.effectworldEffect timeline, tracks, event objects, and loop data
engine\materials\particles_generic.remtTextured-particle material parameters
base\characters\entities\player\player_ma_fpp.entMale-average player first-person components and slots
base\characters\entities\player\player_wa_fpp.entFemale-average player first-person components and slots
base\characters\entities\player\player_ma_tpp.entMale-average player third-person components and slots
base\characters\entities\player\player_wa_tpp.entFemale-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:

  1. the exact depot path and installed game build;
  2. the root type and only the fields or graph edges needed to answer the question;
  3. the identifier domains and external dependencies involved;
  4. whether the shape was observed once or across multiple resources;
  5. 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.

ComponentPinned versionRole
Cyberpunk 2077 for Windows2.31aExact GOG Windows build; CD Projekt RED calls the underlying public patch 2.31
WolvenKit8.19.0Project editing, CR2W inspection, conversion, packing, and deployment
ArchiveXL1.27.0Root questphase, journal, localization, and streaming-block registration
RED4ext1.30.0Runtime required by ArchiveXL
redscript0.5.31Runtime dependency declared by ArchiveXL 1.27.0, even though Labs 1–5 contain no .reds source
TweakXL1.11.3Scope-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.

DomainLab 1 valueRule
Mod namespacecqaShared namespace for tutorial-owned resources
Quest IDcqa001Three-letter prefix plus a three-digit lab number
Depot rootmod\cqa\cqa001\Keeps every Lab 1 resource under one mod-owned root
Completion factcqa001_completedQuest ID first; descriptive suffix second
Localization keyscqa_cqa001_*Namespace and quest remain visible in global lookup tables
WolvenKit projectCQA_Lab01_OneShotHuman-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

ViewUse it to understand
Story flowWhat the player does and sees
Exact node graphWhich node types and sockets implement the route
Resource mapWhich 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.