modwright 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +41 -2
  2. package/bridges/cp2077-cet/ModWrightBridge/probes.lua +113 -0
  3. package/bridges/cp2077-cet/README.md +3 -1
  4. package/bridges/cp2077-cet/bridge.json +1 -1
  5. package/dist/core/build/stage.js +3 -2
  6. package/dist/core/ini.js +26 -0
  7. package/dist/core/knowledge/facts.js +0 -12
  8. package/dist/core/logs/registry.js +19 -0
  9. package/dist/core/logs/scan.js +44 -5
  10. package/dist/core/logs/triage.js +79 -2
  11. package/dist/core/managers/mo2.js +121 -0
  12. package/dist/core/managers/vortex.js +52 -0
  13. package/dist/core/ownership/index.js +364 -0
  14. package/dist/core/project/index.js +1 -1
  15. package/dist/core/project/load.js +5 -0
  16. package/dist/core/testplan/registry.js +51 -2
  17. package/dist/core/writes/index.js +206 -0
  18. package/dist/index.js +0 -2
  19. package/dist/server.js +142 -13
  20. package/dist/surfaces/baldursgate3/surface.js +3 -0
  21. package/dist/surfaces/baldursgate3/validators/story.js +3 -3
  22. package/dist/surfaces/baldursgate3/writes.js +152 -0
  23. package/dist/surfaces/cyberpunk2077/index/parsers/tweakxl-yaml.js +2 -2
  24. package/dist/surfaces/cyberpunk2077/logs.js +82 -3
  25. package/dist/surfaces/cyberpunk2077/surface.js +11 -0
  26. package/dist/surfaces/cyberpunk2077/writes.js +67 -0
  27. package/dist/surfaces/skyrimse/loadorder.js +2 -26
  28. package/dist/surfaces/skyrimse/surface.js +1 -0
  29. package/dist/surfaces/stardewvalley/logs.js +41 -0
  30. package/dist/surfaces/stardewvalley/surface.js +2 -0
  31. package/dist/surfaces/stardewvalley/writes.js +136 -0
  32. package/knowledge/baldursgate3/dialogue.osiris-goals.yaml +29 -0
  33. package/knowledge/baldursgate3/osiris.tags.yaml +78 -0
  34. package/knowledge/cyberpunk2077/cet.rtti-binding.yaml +227 -0
  35. package/knowledge/cyberpunk2077/cet.sandbox.yaml +22 -0
  36. package/knowledge/cyberpunk2077/codeware.overview.yaml +63 -16
  37. package/knowledge/cyberpunk2077/codeware.systems.yaml +152 -33
  38. package/knowledge/cyberpunk2077/entities.queries.yaml +275 -0
  39. package/knowledge/cyberpunk2077/redhottools.hotreload.yaml +30 -4
  40. package/knowledge/cyberpunk2077/rtti.game-systems.yaml +224 -0
  41. package/package.json +1 -1
@@ -15,20 +15,42 @@ facts:
15
15
  - redscript
16
16
  - cet
17
17
  - red4ext
18
- detail: "Source corrected 2026-09-07: RED4ext is listed under Installation, not Compatibility, and
19
- the previous claim's parenthetical \"(per its own README, version 1.18.0 wiki)\" conflated two
18
+ detail: >-
19
+ Source corrected 2026-09-07: RED4ext is listed under Installation, not Compatibility, and the
20
+ previous claim's parenthetical "(per its own README, version 1.18.0 wiki)" conflated two
20
21
  things. 1.18.0 is the version stamped on line 1 of the Codeware wiki's Home page, and is what
21
22
  every API fact in this file was read against; it is not a floor. Framework versions stay in
22
- the claim; \"Cyberpunk 2077 2.31\" is the game version this Codeware release targets, not an
23
+ the claim; "Cyberpunk 2077 2.31" is the game version this Codeware release targets, not an
23
24
  observation date, so there is no `verified_on`. This instantiates the general
24
- cp2077.packaging.version-matrix-breakage."
25
+ cp2077.packaging.version-matrix-breakage.
26
+
27
+ Checked against commit 3a43182 (2026-08-28): README.md's Compatibility and Installation
28
+ sections are byte-for-byte unchanged (game 2.31, redscript 0.5.31+, CET 1.37.0+, RED4ext
29
+ 1.29.0+) — neither confirmed nor contradicted by executable code, since these are floors on
30
+ other projects' version numbers, not something Codeware's own build asserts. xmake.lua's
31
+ `set_version("1.20.4", ...)` is a fourth, unrelated axis: Codeware's own release version, now
32
+ ahead of the "1.18.0" still stamped on the wiki's Home page at this same commit — the wiki
33
+ page has not been bumped to match the 1.20.4 project version.
25
34
  - id: cp2077.codeware.systems.service-persists-between-sessions
26
35
  claim: '"Services are singletons that created on scripts initialization and, unlike scriptable
27
36
  systems, live between game sessions. To create your own service, you have to define a class
28
37
  derived from `ScriptableService`." The instance is reached with
29
38
  `GameInstance.GetScriptableServiceContainer().GetService(n"MyService") as MyService`.'
30
- status: community
31
- source: cp2077-codeware wiki Home, section "Lifecycle > Scriptable services"
39
+ status: inferred
40
+ source: "cp2077-codeware wiki Home, section \"Lifecycle > Scriptable services\"; confirmed against
41
+ commit 3a43182 in scripts/Scripting/ScriptableService.reds (`public abstract native class
42
+ ScriptableService` with commented-out `OnLoad`/`OnReload`/ `OnInitialize`/`OnUninitialize`
43
+ callback signatures) and scripts/Scripting/ScriptableServiceContainer.reds (`public abstract
44
+ native class ScriptableServiceContainer extends IGameSystem { public native func
45
+ GetService(name: CName) -> ref<ScriptableService> }`, reached via `@addMethod(GameInstance)
46
+ public static native func GetScriptableServiceContainer()`). The cross-session persistence is
47
+ implemented in src/App/Scripting/ScriptableServiceContainer.cpp: `OnInitializeScripts`
48
+ instantiates every non-abstract `ScriptableService` subclass and calls `OnLoad` (or `OnReload`
49
+ if `m_scriptsLoaded` is already true, distinguishing the two lifecycle events exactly as the
50
+ wiki describes); `LoadState`/`SaveState` (de)serialize `m_services` to a `.dat` file under
51
+ Codeware's own persistent-data directory (`s_stateDir`, separate from any save file) via
52
+ `Red::ObjectSerializer`, with `SaveState` called from `OnInitializeGameInstance`,
53
+ `OnBeforeWorldDetach` and `OnBeforeGameSave`."
32
54
  tags:
33
55
  - codeware
34
56
  - scriptableservice
@@ -42,8 +64,16 @@ facts:
42
64
  claim: '"If you declare `MyService` in a module, you must include the full path of the module to get
43
65
  the service" — a service declared in `module MyModule.Services` is fetched as
44
66
  `GetService(n"MyModule.Services.MyService")`.'
45
- status: community
46
- source: cp2077-codeware wiki Home, section "Lifecycle > Scriptable services"
67
+ status: inferred
68
+ source: "cp2077-codeware wiki Home, section \"Lifecycle > Scriptable services\"; confirmed against
69
+ commit 3a43182: `GetService` in src/App/Scripting/ScriptableServiceContainer.cpp looks the
70
+ service up by exact `Red::CName` key
71
+ (`m_services.find(aType)`/`m_services.emplace(serviceType->name, ...)`), where
72
+ `serviceType->name` is the RTTI class's full name as registered by the redscript compiler —
73
+ module-qualified for a class declared inside a `module` block. The same full-path pattern is
74
+ used elsewhere in Codeware's own scripts, e.g. scripts/Localization/LocalizationSystem.reds's
75
+ `GameInstance.GetScriptableSystemsContainer(game).Get(n\"Codeware.Localization.LocalizationSy\
76
+ stem\")`."
47
77
  tags:
48
78
  - codeware
49
79
  - scriptableservice
@@ -56,7 +86,15 @@ facts:
56
86
  systems, service state storage is not tied to save files." "Persistence is available for all
57
87
  types except `String`, `ResRef`, `Variant`."'
58
88
  status: community
59
- source: cp2077-codeware wiki Home, section "Lifecycle > Scriptable services"
89
+ source: "cp2077-codeware wiki Home, section \"Lifecycle > Scriptable services\". Checked against
90
+ commit 3a43182: found the `persistent` field qualifier handled by the generic
91
+ `RTTI_PERSISTENT`/`RTTI_PERSISTENT_FQN` macros in the vendored
92
+ lib/Red/TypeInfo/Macros/Definition.hpp, which Codeware uses on its own internal state
93
+ (`RTTI_DEFINE_CLASS(App::ScriptableServiceContainerState, { RTTI_PERSISTENT(services); })` in
94
+ src/App/Scripting/ScriptableServiceContainer.hpp) — but no C++ in src/App enumerates or
95
+ enforces a `String`/`ResRef`/`Variant` exclusion list for user-declared `persistent` fields on
96
+ mod services. That exclusion looks like native engine/redscript-compiler behavior outside
97
+ Codeware's own source, so this claim is neither confirmed nor contradicted here."
60
98
  tags:
61
99
  - codeware
62
100
  - persistent
@@ -70,7 +108,12 @@ facts:
70
108
  are reset to defaults on each game launch. It's safe to change persistent data structure at
71
109
  any time.\""
72
110
  status: community
73
- source: cp2077-codeware wiki Home, section "Lifecycle > Scriptable services"
111
+ source: 'cp2077-codeware wiki Home, section "Lifecycle > Scriptable services". Checked against
112
+ commit 3a43182: this describes how the native object serializer restores only
113
+ `persistent`-flagged fields (see cp2077.codeware.systems.persistent-field-type-exclusions for
114
+ what was found on that mechanism); no Codeware C++ specifically implements the "unmarked
115
+ fields reset to defaults" behavior for user service objects beyond relying on that native
116
+ (de)serialization, so it is left unconfirmed rather than promoted.'
74
117
  tags:
75
118
  - codeware
76
119
  - persistent
@@ -83,8 +126,9 @@ facts:
83
126
  disposed when game session ends. An unreleased reference can interfere with normal object
84
127
  disposal and lead to bugs such as disappearing sounds or crashes."'
85
128
  status: community
86
- source: cp2077-codeware wiki Home, section "Lifecycle > Scriptable services" (the "Referencing game
87
- objects" blockquote)
129
+ source: 'cp2077-codeware wiki Home, section "Lifecycle > Scriptable services" (the "Referencing game
130
+ objects" blockquote). Checked against commit 3a43182: this is a caution in prose with no
131
+ single API to test it against — left community.'
88
132
  tags:
89
133
  - codeware
90
134
  - scriptableservice
@@ -102,8 +146,26 @@ facts:
102
146
  `EntityTarget.ID`/`.Type`/`.RecordID(TweakDBID)`/`.Template`/`.Appearance`/`.Definition`,
103
147
  `ComponentTarget.ID`/`.Name`, `InputTarget.Key`/`.Axis`,
104
148
  `inkWidgetTarget.Library`/`.Controller`.
105
- status: community
106
- source: cp2077-codeware wiki Home, section "Lifecycle > Game events"
149
+ status: inferred
150
+ source: "cp2077-codeware wiki Home, section \"Lifecycle > Game events\"; confirmed against commit
151
+ 3a43182 in scripts/Callback/CallbackSystem.reds (`public native class CallbackSystem extends
152
+ IGameSystem` with `RegisterCallback (eventName: CName, target: ref<IScriptable>, function:
153
+ CName, opt sticky: Bool) -> ref<CallbackSystemHandler>`, reached via
154
+ `GameInstance.GetCallbackSystem()`) and scripts/Callback/CallbackSystemHandler.reds
155
+ (`AddTarget(target: ref<CallbackSystemTarget>) -> ref<CallbackSystemHandler>`, `SetRunMode`,
156
+ `SetLifetime`). All 15 event names are exact `Red::CName` constants in
157
+ src/App/Callback/Controllers/*Hook.hpp and src/App/Callback/CallbackSystem.hpp
158
+ (`Resource/Load`, `Resource/PostLoad`, `Session/BeforeStart`, `Session/Start`,
159
+ `Session/Ready`, `Session/BeforeSave`, `Entity/Extract`, `Entity/Assemble`,
160
+ `Entity/Initialize`, `Entity/Reassemble`, `Entity/Attach`, `Entity/Detach`,
161
+ `Component/Toggle`, `Input/Key`, `Input/Axis`, `InkWidget/Spawn` — note CallbackSystem.hpp
162
+ also defines further Session events, `Session/BeforeEnd`/`Session/End`/
163
+ `Session/AfterSave`/`Session/Pause`/`Session/Resume`, not in the wiki's table, consistent with
164
+ the claim's \"include\" wording rather than an exhaustive list). All selectors confirmed in
165
+ scripts/Callback/Targets/*.reds: `ResourceTarget.Path`/`.Type`,
166
+ `EntityTarget.ID`/`.Type`/`.RecordID`/`.Template`/`.Appearance`/ `.Definition`,
167
+ `ComponentTarget.ID`/`.Name`, `InputTarget.Key`/`.Axis`,
168
+ `inkWidgetTarget.Library`/`.Controller`."
107
169
  tags:
108
170
  - codeware
109
171
  - callbacksystem
@@ -112,16 +174,32 @@ facts:
112
174
  - syntax
113
175
  detail: "Caution for a validator: the event table names `Entity/Attach`, but the wiki's own
114
176
  `SetRunMode` example registers `n\"Entity/Attached\"` — the two spellings disagree on the same
115
- page. Callback lifetime: \"When defined inside scriptable service, callback stays registered
116
- until you manually unregister it. When defined in other contexts, callback is auto removed at
117
- the end of the session\", overridable with `.SetLifetime(CallbackLifetime.Forever)` or
118
- `...Session)`. The callback system can be accessed before the game instance is initialized."
177
+ page. Still true at commit 3a43182: wiki/Home.md lines ~152 and ~294 still write
178
+ `n\"Entity/Attached\"` in worked examples, while `Entity/Attach` is the only name any hook
179
+ registers (src/App/Callback/Controllers/EntityAttachHook.hpp) — the wiki's own examples appear
180
+ to reference an event that does not exist; `Entity/Attach` (the table entry) is the one
181
+ confirmed by source. Callback lifetime: \"When defined inside scriptable service, callback
182
+ stays registered until you manually unregister it. When defined in other contexts, callback is
183
+ auto removed at the end of the session\", overridable with
184
+ `.SetLifetime(CallbackLifetime.Forever)` or `...Session)`. Confirmed in
185
+ src/App/Callback/CallbackSystem.cpp's `RegisterCallback`/ `RegisterStaticCallback`: `if
186
+ (aSticky || (aFrame && aFrame->context &&
187
+ Red::IsInstanceOf<ScriptableService>(aFrame->context)))
188
+ handler->SetLifetime(CallbackLifetime::Forever);` — a callback registered from inside a
189
+ `ScriptableService` gets `Forever` by default; otherwise it defaults to
190
+ `CallbackLifetime.Session` (value `0` in scripts/Callback/CallbackLifetime.reds) and is torn
191
+ down with the session. The callback system can be accessed before the game instance is
192
+ initialized."
119
193
  - id: cp2077.codeware.systems.callback-once-run-mode
120
194
  claim: '"You can make callback to auto unregister after being fired by calling
121
195
  `.SetRunMode(CallbackRunMode.Once)`." `CallbackRunMode.OncePerTarget` instead auto-unregisters
122
196
  "after being fired for each listed target".'
123
- status: community
124
- source: cp2077-codeware wiki Home, section "Lifecycle > Game events"
197
+ status: inferred
198
+ source: 'cp2077-codeware wiki Home, section "Lifecycle > Game events"; confirmed against commit
199
+ 3a43182 in scripts/Callback/CallbackRunMode.reds: `public enum CallbackRunMode { Default = 0,
200
+ Once = 1, OncePerTarget = 2 }`, set via `CallbackSystemHandler.SetRunMode(runMode:
201
+ CallbackRunMode) -> ref<CallbackSystemHandler>` in
202
+ scripts/Callback/CallbackSystemHandler.reds.'
125
203
  tags:
126
204
  - codeware
127
205
  - callbacksystem
@@ -133,21 +211,42 @@ facts:
133
211
  require loading resources you can refresh appearance\" with `comp.RefreshAppearance()`.
134
212
  Supported components are `entMeshComponent`, `entSkinnedMeshComponent`,
135
213
  `entGarmentSkinnedMeshComponent` and `entMorphTargetSkinnedMeshComponent`."
136
- status: community
137
- source: cp2077-codeware wiki Home, section "Entities > Mesh appearances"
214
+ status: inferred
215
+ source: "cp2077-codeware wiki Home, section \"Entities > Mesh appearances\"; confirmed against
216
+ commit 3a43182 in scripts/Entity/IComponent.reds: `@addMethod(IComponent) public native func
217
+ LoadAppearance(opt wait: Bool) -> Bool` and `public native func RefreshAppearance() -> Bool`.
218
+ The exact four supported component types are confirmed in
219
+ src/App/Entity/ComponentWrapper.cpp's constructor, which classifies a component into
220
+ `ComponentType::MeshComponent`, `::SkinnedMeshComponent`, `::GarmentSkinnedMeshComponent` or
221
+ `::MorphTargetSkinnedMeshComponent` (else `::Unsupported`) by RTTI `IsA` checks against
222
+ `Red::ent::MeshComponent`, `Red::ent::SkinnedMeshComponent`,
223
+ `Red::ent::GarmentSkinnedMeshComponent` and `Red::ent::MorphTargetSkinnedMeshComponent` —
224
+ matching `entMeshComponent`/`entSkinnedMeshComponent`/
225
+ `entGarmentSkinnedMeshComponent`/`entMorphTargetSkinnedMeshComponent` exactly."
138
226
  tags:
139
227
  - codeware
140
228
  - loadappearance
141
229
  - refreshappearance
142
230
  - entity-component
143
- detail: The worked example is `comp.mesh *= r"mod\player\dynamic.mesh"; comp.meshAppearance =
144
- n"neon_red"; comp.LoadAppearance();`. Softened on 2026-09-07 from "must be called ... (it
145
- re-resolves resources)" to the wiki's own "you can request it to load".
231
+ detail: "The worked example is `comp.mesh *= r\"mod\\player\\dynamic.mesh\"; comp.meshAppearance =
232
+ n\"neon_red\"; comp.LoadAppearance();`. Softened on 2026-09-07 from \"must be called ... (it
233
+ re-resolves resources)\" to the wiki's own \"you can request it to load\". Confirmed the
234
+ resource re-resolution distinction in src/App/Entity/ComponentEx.cpp: `LoadAppearance` calls
235
+ `ComponentWrapper::LoadResource(/*refresh=*/true, aWait)`, which re-resolves the mesh/morph
236
+ resource path and then refreshes the appearance; `RefreshAppearance` calls
237
+ `ComponentWrapper::RefreshAppearance()` directly without touching the resource path — matching
238
+ the wiki's \"changed mesh/appearance\" vs. \"changed other properties\" split."
146
239
  - id: cp2077.codeware.systems.static-entity-reposition-without-respawn
147
240
  claim: '"Static entities can be moved now without respawning them at new position" via
148
241
  `entity.SetWorldTransform(transform)`.'
149
- status: community
150
- source: cp2077-codeware wiki Home, section "Entities > World transform"
242
+ status: inferred
243
+ source: 'cp2077-codeware wiki Home, section "Entities > World transform"; confirmed against commit
244
+ 3a43182 in scripts/Entity/Entity.reds: `@addMethod(Entity) public native func
245
+ SetWorldTransform(transform: WorldTransform)`. The method is added to the base `Entity` class
246
+ (usable on any entity, not only ones spawned via StaticEntitySystem), implemented in
247
+ src/App/Entity/EntityEx.hpp as `Raw::IPlacedComponent::SetTransform(transformComponent,
248
+ aTransform)` — a direct transform-component update, confirming the "without respawning" claim
249
+ (no despawn/respawn round-trip).'
151
250
  tags:
152
251
  - codeware
153
252
  - setworldtransform
@@ -157,8 +256,17 @@ facts:
157
256
  `playerSystem.GetCustomizationPuppet()`, `GetInventoryPuppet()` and `GetPhotoPuppet()`, from
158
257
  `GameInstance.GetPlayerSystem(GetGameInstance())`, for the customization screen, inventory
159
258
  screen and photo mode respectively.
160
- status: community
161
- source: cp2077-codeware wiki Home, section "Player > Accessing player objects"
259
+ status: inferred
260
+ source: "cp2077-codeware wiki Home, section \"Player > Accessing player objects\"; confirmed against
261
+ commit 3a43182 in scripts/Player/PlayerSystem.reds: `@addMethod(PlayerSystem) public func
262
+ GetCustomizationPuppet() -> wref<gamePuppet>`, `GetInventoryPuppet() -> wref<gamePuppet>` and
263
+ `GetPhotoPuppet() -> wref<gamePuppet>`, each backed by an `@addField`-ed `wref<gamePuppet>`
264
+ set via `@wrapMethod(inkPuppetPreviewGameController) OnPreviewInitialized`
265
+ (customization/inventory, keyed on the controller's class name
266
+ `gameuiCharacterCreationPuppetPreviewGameController`/
267
+ `gameuiInventoryPuppetPreviewGameController`) and `@wrapMethod(PhotoModePlayerEntityComponent)
268
+ SetupInventory` (photo mode) — matching the claimed customization/inventory/photo-mode mapping
269
+ exactly."
162
270
  tags:
163
271
  - codeware
164
272
  - playersystem
@@ -168,8 +276,12 @@ facts:
168
276
  - id: cp2077.codeware.systems.wardrobe-forget-item
169
277
  claim: '"Items can be permanently removed from wardrobe" with
170
278
  `GameInstance.GetWardrobeSystem(GetGameInstance()).ForgetItemID(ItemID.FromTDBID("Items.Glasses_03_basic_09"))`.'
171
- status: community
172
- source: cp2077-codeware wiki Home, section "Player > Managing wardrobe"
279
+ status: inferred
280
+ source: 'cp2077-codeware wiki Home, section "Player > Managing wardrobe"; confirmed against commit
281
+ 3a43182 in scripts/Player/WardrobeSystem.reds: `@addMethod(WardrobeSystem) public native func
282
+ ForgetItemID(itemID: ItemID) -> Bool` (the only member Codeware adds to `WardrobeSystem`).
283
+ `ItemID.FromTDBID` itself is a base-game function (`gameItemID.FromTDBID`, called via
284
+ `Red::CallStatic` in src/App/World/OpenWorldSystem.cpp), not something Codeware defines.'
173
285
  tags:
174
286
  - codeware
175
287
  - wardrobesystem
@@ -181,8 +293,15 @@ facts:
181
293
  retrieved at runtime\" —
182
294
  `GameInstance.GetVehicleSystem(GetGameInstance()).EnablePlayerVehicleID(t\"Vehicle.v_sport2_q\
183
295
  uadra_type66_avenger_player\", true)`."
184
- status: community
185
- source: cp2077-codeware wiki Home, section "Player > Unlocking vehicles"
296
+ status: inferred
297
+ source: 'cp2077-codeware wiki Home, section "Player > Unlocking vehicles"; confirmed against commit
298
+ 3a43182 in scripts/Vehicle/VehicleSystem.reds: `@addMethod(VehicleSystem) public func
299
+ EnablePlayerVehicleID(vehicleID: TweakDBID, enable: Bool, opt despawnIfDisabling: Bool) ->
300
+ Bool`, which checks `vehicleID` against
301
+ `TweakDBInterface.GetForeignKeyArray(t"Vehicle.vehicle_list.list")` before calling the native
302
+ `ToggleGarageVehicle(garageID: GarageVehicleID, enable: Bool) -> Bool`. Note the actual
303
+ signature has a third, optional `despawnIfDisabling: Bool` parameter not mentioned in the
304
+ claim (despawns the vehicle if disabling succeeds).'
186
305
  tags:
187
306
  - codeware
188
307
  - vehiclesystem
@@ -0,0 +1,275 @@
1
+ game: cyberpunk2077
2
+ topic: entities.queries
3
+ title: "Finding entities at runtime: what the dynamic-entity tag registry does and does not see, and
4
+ the RTTI-confirmed spatial query methods"
5
+ facts:
6
+ - id: cp2077.entities.gettaggedids-is-a-dynamic-entity-registry-not-a-node-lookup
7
+ claim: "Game.GetDynamicEntitySystem():GetTaggedIDs(tag) is Codeware's DynamicEntitySystem, and it
8
+ returns only entities that system itself created (DynamicEntitySpec.tags) or was told about
9
+ with AssignTag. A sector-placed static prop (a worldEntityNode in a .streamingsector) was not
10
+ found by it in two in-game checks (2026-08 and 2026-09-09), and the source shows why: nothing
11
+ a streaming sector spawns is ever entered into that registry."
12
+ status: verified
13
+ verified_on: "2.31"
14
+ source: ModWright field verification
15
+ tags:
16
+ - entity
17
+ - tag
18
+ - gettaggedids
19
+ - dynamic-entity
20
+ - codeware
21
+ - world-placement
22
+ - gotcha
23
+ related:
24
+ - cp2077.entities.native-gametagsystem-finds-entities-by-tag
25
+ - cp2077.entities.codeware-staticentitysystem-spawns-tagged-static-entities
26
+ detail: "What is verified in-engine is the negative: an untagged sector node is invisible to
27
+ GetTaggedIDs, and the tags the test mod used only ever marked entities its earlier experiments
28
+ spawned dynamically (which is why its cleanup script deletes them by tag). A claim briefly
29
+ recorded as verified on 2026-09-10 and corrected the same day — that setting the sector node's
30
+ `tag` CName would make GetTaggedIDs find it — is ruled out by the source: the registry is
31
+ populated from DynamicEntitySpec.tags and AssignTag calls only, never from streaming-sector
32
+ node data or from the native per-object tag list. Codeware does mirror its tags into the
33
+ native side on spawn (DynamicEntitySystem.cpp: gameObject->tags.Add on spawn; AssignTag also
34
+ calls Raw::TagSystem::AssignTag), but the direction is Codeware -> engine, not engine ->
35
+ Codeware. So \"tag the node, then GetTaggedIDs\" is dead; the live question is whether a node
36
+ tag reaches the *native* tag system, which is the next fact. The scaffold
37
+ cyberpunk2077.new-sector-node therefore ships `tag: None` (the vanilla entSpawner export
38
+ shape) and keeps its N-1 row manual."
39
+ - id: cp2077.entities.native-gametagsystem-finds-entities-by-tag
40
+ claim: "The shipped game has its own tag lookup, separate from Codeware's: gameGameTagSystem
41
+ (obtained via ScriptGameInstance::GetGameTagSystem, so from CET presumably
42
+ Game.GetGameTagSystem()) exposes GetAnyMatchingEntity(tag: CName) -> ref<entEntity> and
43
+ GetAllMatchingEntities(tag: CName, out entities: array<ref<entEntity>>) -> Bool; gameObject
44
+ carries a native `tags: redTagList` field and a native HasTag(tag: CName) -> Bool method.
45
+ Whether a worldEntityNode's `tag` field lands in that list or that system is untested."
46
+ status: unverified
47
+ source: "rayshader/cp2077-nativedb@b5d29af classes.json: class gameGameTagSystem (parent
48
+ gameIGameSystem) with exactly those two functions; ScriptGameInstance function
49
+ GetGameTagSystem returning ref<gameGameTagSystem>; class gameObject field tags of type
50
+ redTagList and function HasTag(tag: CName) -> Bool; class redTagSystem (no scripted
51
+ functions). psiberx/cp2077-codeware@3a43182 src/Red/TagSystem.hpp wraps the engine's
52
+ TagSystem_AssignTag/UnassignTag by address and scripts/Entity/GameObject.reds adds
53
+ `AddTag(tag: CName)` to GameObject — so the native list is real and writable. Read 2026-09-10;
54
+ nothing on disk calls GetGameTagSystem from Lua or redscript. Negative searches (2026-09-11):
55
+ CDPR-Modding-Documentation/Cyberpunk-Modding-Docs at 4e798d6 (591 pages) and NativeDB-wiki at
56
+ ed4e34d document neither this system nor a world node's tag/tagExt fields."
57
+ tags:
58
+ - entity
59
+ - tag
60
+ - gametagsystem
61
+ - hastag
62
+ - rtti
63
+ - world-placement
64
+ - live-check-next
65
+ related:
66
+ - cp2077.entities.gettaggedids-is-a-dynamic-entity-registry-not-a-node-lookup
67
+ detail: >-
68
+ This is the mechanism an auto-tag idea would actually need, and it is a hypothesis with two
69
+ halves to test: (1) does the CET global Game.GetGameTagSystem() resolve, and does
70
+ GetAnyMatchingEntity find an entity Codeware tagged (a known-positive control: spawn or
71
+ AssignTag something with Codeware, then look it up natively — Codeware mirrors its tags into
72
+ the native system, per the previous fact); (2) does a sector node authored with a non-None
73
+ `tag` come back from the same call, or at least answer obj:HasTag(tag) once the object is in
74
+ hand. Console lines (save loaded, standing near a placed prop whose node has a tag, and near
75
+ any Codeware-tagged dynamic entity):
76
+ local ts = Game.GetGameTagSystem(); print(type(ts))
77
+ local e = ts:GetAnyMatchingEntity("<codeware tag>"); print(e, e and e:GetClassName())
78
+ local ok, all = ts:GetAllMatchingEntities("<node tag>"); print(ok, type(all), all and all[1] and all[1]:GetClassName())
79
+ local o = Game.GetTargetingSystem():GetLookAtObject(Game.GetPlayer(), true, false); print(o and o:GetClassName(), o and o:HasTag("<node tag>"), o and o:GetCurrentAppearanceName())
80
+ GetAllMatchingEntities has an out-parameter, which CET returns as a second value after the
81
+ Bool (cp2077.cet.member-calls-via-colon-and-out-params-as-extra-returns), hence `ok, all`. The
82
+ last line is also the cheapest way yet found to get a handle on a placed prop with no position
83
+ probe at all: look at it. GetLookAtObject(instigator: wref<gameObject>, withLOS: Bool,
84
+ ignoreTranparent: Bool) -> ref<gameObject> is RTTI-listed on gametargetingTargetingSystem
85
+ (cp2077.rtti.targetingsystem-crosshair-and-distance-queries; an earlier draft of this line
86
+ named GetObjectClosestToCrosshair, which actually takes an instigator, an EulerAngles and a
87
+ gameTargetSearchQuery) and is unattested from Lua. If half (2) passes, the scaffold can tag
88
+ its nodes again and N-1 becomes an automated row against a *new* probe kind over
89
+ GetGameTagSystem, not over cp2077.entity.tagged; if it fails, node tags are a dead end and
90
+ only the position-based calls remain.
91
+ - id: cp2077.entities.codeware-staticentitysystem-spawns-tagged-static-entities
92
+ claim: 'Codeware also ships a StaticEntitySystem (Game.GetStaticEntitySystem() by the same
93
+ @addMethod pattern): SpawnEntity(StaticEntitySpec{templatePath, appearanceName, position,
94
+ orientation, attached, tags}) spawns an entity "the way world nodes usually do", with its own
95
+ tag registry (IsPopulated/GetTagged/GetTaggedIDs/DespawnTagged/AttachTagged/DetachTagged), no
96
+ persistent state, and no distance-based visibility. Its tag registry is as private as the
97
+ dynamic one: it does not see sector-placed nodes either.'
98
+ status: inferred
99
+ source: psiberx/cp2077-codeware@3a43182 wiki/Home.md "Spawning static entities" (option table and
100
+ ladder example); scripts/World/StaticEntitySystem.reds and StaticEntitySpec.reds;
101
+ src/App/World/StaticEntitySystem.cpp GetTaggedIDs() reads m_entityIDsByTag only; the
102
+ callback-target table lists StaticEntityTarget.Tag(CName) alongside
103
+ DynamicEntityTarget.Tag(CName).
104
+ tags:
105
+ - entity
106
+ - codeware
107
+ - static-entity
108
+ - spawn
109
+ - tag
110
+ - world-placement
111
+ related:
112
+ - cp2077.entities.gettaggedids-is-a-dynamic-entity-registry-not-a-node-lookup
113
+ - cp2077.loot.streaming-sector-required-for-loot
114
+ detail: Recorded because it is the third tagged spawn path and easy to confuse with the other two
115
+ when a probe says "tagged". It is not a replacement for sector placement — the
116
+ streaming-sector rule (cp2077.loot.streaming-sector-required-for-loot) is about loot
117
+ interactions on containers, which no script-side spawn path has been shown to provide — but
118
+ for a placement a mod is willing to make at runtime it would come with verification built in
119
+ (its own GetTaggedIDs). ModWright's CET bridge does not call it.
120
+ - id: cp2077.entities.gameobject-getnpcsaroundobject-exists-in-rtti
121
+ claim: "gameObject (parent entGameEntity; every puppet and most world objects descend from it)
122
+ carries the RTTI-registered method GetNPCsAroundObject(range: Float) returning
123
+ array<ref<NPCPuppet>> — NPCs within that range of the object it is called on."
124
+ status: inferred
125
+ source: 'rayshader/cp2077-nativedb@b5d29af classes.json, class entry {"a": "entGameEntity", "b":
126
+ "gameObject"} ("a" is the parent, "b" the class name in this dump), function entry {"a":
127
+ "GetNPCsAroundObject;Float", "e": [{"b": "range"}], "c": array<ref<NPCPuppet>>}. Read from the
128
+ committed dump on 2026-09-10 and re-checked 2026-10-04; the dumper was not run.'
129
+ tags:
130
+ - entity
131
+ - spatial-query
132
+ - rtti
133
+ - npc
134
+ - unverified-call
135
+ detail: "The dump is real reflection data from the shipped build, so the method's existence and
136
+ signature are solid. An earlier version of this fact placed the method on entGameEntity; that
137
+ came from reading the dump's \"a\"/\"b\" keys backwards. Not confirmed: that CET exposes it as
138
+ a Lua member call (`obj:GetNPCsAroundObject(r)` is assumed, matching every other RTTI method
139
+ the bridge calls that way) and what the returned array looks like from Lua. No CET mod or wiki
140
+ page calls it. ModWright's cp2077.entity.nearby probe kind attempts this call live,
141
+ pcall-wrapped, and stays unverified until a live run exercises it."
142
+ - id: cp2077.entities.gameobject-getentitiesaroundobject-filter-constructors-exist
143
+ claim: "gameObject also carries GetEntitiesAroundObject(range: Float, searchFilter:
144
+ gameTargetSearchFilter) returning array<ref<entEntity>> — any entity type, filtered.
145
+ gameTargetSearchFilter is an opaque native struct (a class entry with no listed fields), but
146
+ the same dump's globals.json lists constructor functions that return one: TSF_All(mask),
147
+ TSF_Any(mask), TSF_Not(mask), TSF_And(tsf1..tsf4), TSF_Or(tsf1..tsf4), TSF_NPC(),
148
+ TSF_EnemyNPC(), TSF_Quickhackable(); the mask type is
149
+ gametargetingSystemSearchFilterMaskValue."
150
+ status: unverified
151
+ source: 'rayshader/cp2077-nativedb@b5d29af classes.json: gameObject function entry {"a":
152
+ "GetEntitiesAroundObject;FloatTargetSearchFilter", "e": [{"b": "range"}, {"b": "searchFilter",
153
+ "a": {"b": "gameTargetSearchFilter"}}]}; class entry {"b": "gameTargetSearchFilter"} with zero
154
+ fields and zero functions; globals.json entries TSF_All, TSF_Any, TSF_Not, TSF_And, TSF_Or,
155
+ TSF_EnemyNPC, TSF_NPC, TSF_Quickhackable, each with return type gameTargetSearchFilter;
156
+ enums.json gametargetingSystemSearchFilterMaskValue. Also in classes.json:
157
+ gameTargetSearchQuery (fields testedSet, searchFilter, includeSecondaryTargets,
158
+ ignoreInstigator, maxDistance, filterObjectByDistance, queryTarget; SetComponentFilter). Read
159
+ 2026-09-10, re-checked 2026-10-04.'
160
+ tags:
161
+ - entity
162
+ - spatial-query
163
+ - rtti
164
+ - target-search-filter
165
+ - live-check-next
166
+ detail: >-
167
+ This is the call that would verify a world placement that is not an NPC (a sector-placed prop,
168
+ say). An earlier version of this fact said no TargetSearchFilter class appears in the dump and
169
+ that constructing the struct would mean guessing a native layout; both were wrong — the class
170
+ is there (opaque), and the struct is meant to be obtained from the TSF_* constructor globals,
171
+ not built by hand. The Lua side is largely settled from CET's own source (the cp2077.cet.*
172
+ binding facts, 2026-09-11): non-member RTTI globals are exposed as bare names, as Game.Name
173
+ and as Game["Name;"]; a returned struct is a ClassReference copy that passes straight back
174
+ into a struct-typed parameter; an array return is a 1-based table whose null handles become
175
+ nil holes. The mask enum is in the dump too: enums.json
176
+ gametargetingSystemSearchFilterMaskValue (alias TSFMV): Obj_Player=1, Obj_Puppet=2,
177
+ Obj_Sensor=4, Obj_Device=8, Obj_Other=16, Att_Friendly=32, Att_Hostile=64, Att_Neutral=128,
178
+ Sp_AimAssistEnabled=256, Sp_Aggressive=512, St_Alive=2048, St_Dead=4096, St_NotDefeated=8192,
179
+ St_Defeated=16384, St_Conscious=32768, St_Unconscious=65536, St_TurnedOn=131072,
180
+ St_TurnedOff=262144, St_QuickHackable=524288, St_AliveAndActive=174080. CET coerces an
181
+ undeclared enum value to 0 silently
182
+ (cp2077.cet.enum-params-accept-number-string-or-enum-but-undeclared-values-become-zero), so
183
+ "every object" cannot be one ORed mask; it is TSF_Or(TSF_Any(E"Obj_Device"),
184
+ TSF_Any(E"Obj_Other"), TSF_Any(E"Obj_Puppet"), TSF_Any(E"Obj_Sensor")) with each member built
185
+ by Enum.new("gametargetingSystemSearchFilterMaskValue", name), the one form that fails loudly
186
+ on a wrong type name. Still unverified: whether the game marks TSF_* isExec (which would drop
187
+ only the bare-global form), the semantics of TSF_Any vs TSF_All and TSF_Or, whether a static
188
+ prop is in the targeting set at all (a loot-case template that carries a
189
+ gameTargetingComponent is the likely case), and what the calls return on a real prop.
190
+ ModWright's cp2077.entity.nearby handler attempts exactly this behind `filter: "objects"`,
191
+ pcall-wrapped. Console lines (a save loaded, standing near a known placed entity), counting
192
+ with pairs because of the nil holes:
193
+ print(type(TSF_NPC), type(TSF_All), type(Game.TSF_NPC), type(Game["TSF_NPC;"]))
194
+ local f = TSF_NPC(); print(type(f))
195
+ local r = Game.GetPlayer():GetNPCsAroundObject(20.0); local n=0; for _ in pairs(r or {}) do n=n+1 end; print(type(r), n)
196
+ local e = Game.GetPlayer():GetEntitiesAroundObject(20.0, f); local m=0; for _ in pairs(e or {}) do m=m+1 end; print(type(e), m, e and e[1] and e[1]:GetClassName())
197
+ local E = function(n) return Enum.new("gametargetingSystemSearchFilterMaskValue", n) end
198
+ local g = TSF_Or(TSF_Any(E("Obj_Device")), TSF_Any(E("Obj_Other")), TSF_Any(E("Obj_Puppet")), TSF_Any(E("Obj_Sensor")))
199
+ local a = Game.GetPlayer():GetEntitiesAroundObject(20.0, g); local k=0; for _,x in pairs(a or {}) do k=k+1; print(x:GetClassName()) end; print(type(a), k)
200
+ Record each line's exact output.
201
+ - id: cp2077.entities.spatial-and-targeting-systems-exist-but-are-heavier
202
+ claim: "Two other RTTI-registered systems expose broader spatial/targeting capability:
203
+ gameISpatialQueriesSystem (Overlap/OverlapByQueryFilter/SyncRaycastBy* — physics-engine
204
+ overlap and raycast queries) and gametargetingTargetingSystem (the concrete class behind
205
+ ScriptGameInstance::GetTargetingSystem; gameITargetingSystem is its empty interface parent):
206
+ GetTargetClosestByDistance, GetObjectClosestToCrosshair(instigator, angleDistance, query),
207
+ GetLookAtObject(instigator, withLOS, ignoreTranparent) -> ref<gameObject>, GetTargetParts, and
208
+ other player-aim-centric lookups (signatures in
209
+ cp2077.rtti.targetingsystem-crosshair-and-distance-queries). Neither is a simple \"list of
210
+ entities near a point\" call the way GetEntitiesAroundObject's name suggests; both need more
211
+ setup (a query shape or filter, or player-aim context) than a probe should invent blind."
212
+ status: unverified
213
+ source: rayshader/cp2077-nativedb@b5d29af classes.json, gameISpatialQueriesSystem and
214
+ gametargetingTargetingSystem class entries (captured 2026-09-10; class name corrected
215
+ 2026-09-11 — the functions live on the concrete class, not the interface).
216
+ tags:
217
+ - entity
218
+ - spatial-query
219
+ - rtti
220
+ - targeting-system
221
+ - future-work
222
+ detail: "Recorded so a future session does not re-derive this from scratch: these exist and are real
223
+ engine capability, but gameObject's own GetEntitiesAroundObject/GetNPCsAroundObject are the
224
+ more directly applicable pair for \"what did ModWright place nearby\" and were pursued first
225
+ for that reason. GetTargetParts takes a gameTargetSearchQuery, whose searchFilter field is the
226
+ same gameTargetSearchFilter the TSF_* constructors produce."
227
+ - id: cp2077.entities.ententity-getcurrentappearancename-exists-in-rtti
228
+ claim: "entEntity carries GetCurrentAppearanceName() -> CName (no parameters) and
229
+ GetCurrentColorVariantName() -> CName; entIComponent carries GetAppearanceName() -> CName. A
230
+ sector-placed loot-case prop inherits the entity-level read: a template whose entity class is
231
+ LootContainerObjectAnimatedByTransform reaches entEntity through its parent chain."
232
+ status: inferred
233
+ source: ModWright research
234
+ tags:
235
+ - entity
236
+ - appearance
237
+ - rtti
238
+ - unverified-call
239
+ - live-check-next
240
+ related:
241
+ - cp2077.entities.native-gametagsystem-finds-entities-by-tag
242
+ detail: This is a direct read of the question cp2077.entity.appearance asks by differential
243
+ inference (did the sector node's appearanceName resolve?). One scalar return, the same risk
244
+ class as GetWorldYaw. Once any handle to the placed prop is in hand (GetLookAtObject, a native
245
+ tag lookup, or a position query), `obj:GetCurrentAppearanceName()` compared with the node's
246
+ authored appearanceName is the whole check. Registered nowhere yet; the probe registry's
247
+ cp2077.entity.appearance row names it as the candidate for the next live run.
248
+ - id: cp2077.entities.loot-case-template-component-names
249
+ claim: 'A sector-placed loot-case entity template (entity class
250
+ LootContainerObjectAnimatedByTransform) declares eleven components, by class and CName:
251
+ gameInventory "inventory", gameinteractionsComponent "interactions", gameScanningComponent
252
+ "scanning", WorkspotMapperComponent "workspotMapper", entPlaceholderComponent "root",
253
+ gameaudioSoundComponent "audioSound", gameVisionModeComponent "vision", entSlotComponent
254
+ "UI_Slots", GameplayRoleComponent "GameplayRole", entEffectSpawnerComponent "state_fxes",
255
+ gameTargetingComponent "targeting". A light-prop template declares one, a gameLightComponent
256
+ under a mod-chosen name.'
257
+ status: inferred
258
+ source: ModWright research
259
+ tags:
260
+ - entity
261
+ - component
262
+ - template
263
+ - interaction
264
+ - loot-container
265
+ related:
266
+ - cp2077.entities.ententity-getcurrentappearancename-exists-in-rtti
267
+ detail: Design-time template fields from one mod's loot case and another's light prop, not an
268
+ in-engine observation that each component resolves on the streamed instance; the registry rows
269
+ that ask for component names stay unverified, but no longer have to say the interaction name
270
+ is unattested anywhere. The light-prop mod's CET script calls ent:FindComponentByName(<its
271
+ light's name>) — a live call against a name taken from the template, which is the pattern
272
+ cp2077.entity.components and cp2077.interaction.present rely on. The presence of
273
+ gameTargetingComponent "targeting" on the case is also the first evidence that a sector-placed
274
+ prop of this kind is in the targeting set, which the GetEntitiesAroundObject and
275
+ GetLookAtObject lines depend on.
@@ -199,10 +199,12 @@ facts:
199
199
  reload completed" after the trigger — see the archives read-back and
200
200
  hot-folder-moves-into-mods); the `.hot-tweaks` half is VERIFIED INEFFECTIVE — the sentinel is
201
201
  deleted/consumed but no TweakXL.Reload follows (see hot-tweaks-consumed-without-reload-1.3.0);
202
- `.hot-scripts` is still untested. So "ModWright can drive all three" holds only for archives;
203
- tweaks must go through TweakXL.Reload() via cp2077.lua.eval. RELATED:
204
- cp2077.redhottools.hotreload.hot-folder-moves-into-mods (the archive half) and
205
- cp2077.process.assetbuild.rht-hot-folder-banned-for-whole-mesh-archives.'
202
+ `.hot-scripts` drove real script reloads on 2026-09-13, but only seen fired together with a
203
+ second trigger, and that run ended in a crash (see
204
+ reloadscripts-recompiles-cleanly-storm-crash-1.3.0). So "ModWright can drive all three" holds
205
+ for archives; tweaks must go through TweakXL.Reload() via cp2077.lua.eval; scripts reload, but
206
+ one trigger at a time. RELATED: cp2077.redhottools.hotreload.hot-folder-moves-into-mods (the
207
+ archive half) and cp2077.process.assetbuild.rht-hot-folder-banned-for-whole-mesh-archives.'
206
208
  - id: cp2077.redhottools.hotreload.hot-tweaks-consumed-without-reload-1.3.0
207
209
  claim: "In Red Hot Tools 1.3.0.33690, dropping `red4ext/plugins/RedHotTools/.hot-tweaks` is consumed
208
210
  (RHT deletes the file) but does NOT drive a TweakXL reload: after an edited `r6/tweaks` file
@@ -243,3 +245,27 @@ facts:
243
245
  (src/core/deploy/reload.ts detectRedHotTools) now gates the RHT-dependent rows on the DLL +
244
246
  the watcher line instead. RHT 1.3.0 is installed and current while the README floors read
245
247
  2.21, so a floor check would have refused it."
248
+ - id: cp2077.redhottools.hotreload.reloadscripts-recompiles-cleanly-storm-crash-1.3.0
249
+ claim: "In Red Hot Tools 1.3.0.33690 a redscript hot reload recompiles and rebinds cleanly: RHT's
250
+ ScriptLoader logs Compiling, Reading blob, Validating, Capturing scriptable data, Binding,
251
+ Translating bytecode, Initializing runtime, Restoring scriptable data and \"Scripts reload
252
+ completed.\" with no compile or bind error. But with a save loaded, firing two script-reload
253
+ triggers at once (the `.hot-scripts` sentinel and the `RedHotTools.ReloadScripts()` facade,
254
+ called from CET Lua) produced about five full recompile-and-rebind cycles in 8 seconds, and
255
+ the game crashed to desktop just after the last one."
256
+ status: verified
257
+ verified_on: "2.31"
258
+ source: ModWright field verification
259
+ tags:
260
+ - redhottools
261
+ - hot-scripts
262
+ - reloadscripts
263
+ - hot-reload
264
+ - crash
265
+ - negative-result
266
+ detail: "Fire exactly one script-reload trigger. ModWright's reload apply writes only the
267
+ `.hot-scripts` sentinel for scripts. Still open: whether one clean reload with a save loaded
268
+ is stable, or whether script reloads belong at the main menu, since RHT rebinds every script
269
+ class over live gameplay objects. RELATED:
270
+ cp2077.redhottools.hotreload.triggers-are-sentinel-files and
271
+ cp2077.cet.sandbox.exec-func-vs-addmethod-reachability."