REDCODE / DOCS

CR2W editor

Draft 0.1, 2026-10-12, for review. The architecture document (design.html, section 07) fixes the shape: a custom editor with a chunk tree, a property grid and a raw JSON view, reading through the bridge in pieces and writing edits as patches, with undo and redo through VS Code’s custom editor API. This page turns that into something that can be built and tested.

What it is for

The owner’s five most used file types, measured on 2026-10-12 against a typical file of each kind from the game archives:

Type File JSON What a modder edits in it Shape of the edit
.ent entity 3.6 KB 79 KB Components: names, mesh paths, transforms, appearances A tree of about 500 nodes. compiledData is a copy that WolvenKit rebuilds on save
.mesh 163 KB 495 KB Appearances, material entries, local material instances A tree of about 1,000 nodes, but 70% of the JSON is two binary buffers (renderResourceBlob, parameters) that are never edited by hand
.inkatlas 2.8 KB 20 KB Slots and their parts: names and clipping rectangles over the atlas texture A short list that only makes sense drawn over the image
onscreens.json localization 8.8 MB 21 MB Entries: primary key, secondary key, female and male text One array of 60,303 rows. A table, not a tree
.xbm texture varies small Setup: compression, group, mip settings. The pixels come from an import A handful of properties next to the viewer that already exists

Two conclusions set the design:

What the user sees

The editor opens for every CR2W extension in the project (the roughly 150 in WolvenKit’s list) as the default editor, with “Open With…” still offering the text editor and Open as JSON. From the Game Assets view the same editor opens read-only on a temporary copy.

┌──────────────────────────────────────────────────────────────────────────────┐
│ t2_091_pma_jacket__racing_dec_sticker.mesh      CMesh   163,118 B   [JSON]   │ header, HUD yellow name
├──────────────────────────┬───────────────────────────────────────────────────┤
│ TREE                     │ PROPERTIES                                        │
│ ▸ appearances (6)        │ appearances[0]                 meshMeshAppearance │
│ ▾ materialEntries (9)    │ ─────────────────────────────────────────────── │
│    ▸ [0] default         │ name            CName   [default            ]   │
│    ▸ [1] glass           │ chunkMaterials  array   9 items              ▸   │
│ ▸ localMaterialBuffer    │ tags            array   0 items              +   │
│ ▸ boneNames (58)         │                                                   │
│   renderResourceBlob     │                                                   │
│   204,918 B buffer       │                                                   │
├──────────────────────────┴───────────────────────────────────────────────────┤
│ Modified: 2 edits   Ctrl+Z undo   search: [            ]   bridge 0.5.0      │
└──────────────────────────────────────────────────────────────────────────────┘

Specialised views

View Opens for What it adds Reuses
Table localizationPersistenceOnScreenEntries (the LocKey files such as onscreens.json, a few big ones) and localizationPersistenceSubtitleEntries (about 300 small files, one per quest or scene) A virtual table over root.Data.entries: the key columns from the row type (primaryKey and secondaryKey for on-screens, stringId for subtitles), then the female and male variants; type-to-filter by key or text, add and delete rows, inline edit. Paged through the bridge 200 rows at a time The same node and patch calls as the tree
Atlas inkTextureAtlas The texture (through texture.toPng of the referenced .xbm) with every part’s clipping rectangle drawn over it. Click a rectangle to select the part, then edit clippingRectInPixels in the grid; the UV rectangle is recomputed from the texture size. Dragging the rectangle’s edges is deferred The texture viewer’s image page
Texture CBitmapTexture The properties grid beside the existing viewer: setup fields only The texture viewer

Each view is a mode of the same webview page, chosen from the root chunk’s $type. The tree is always reachable from the header, so nothing is hidden.

How it reads and writes

Addressing

A node is named by its path from the root chunk: property names and array indices, the same path the JSON uses, so a path is valid in the editor, in the bridge, in the log and in a patch. Examples: components.3.name, root.Data.entries.41023.femaleVariant, slots.Elements.0.parts.2.clippingRectInPixels. A handle’s object is reached through its property, not through a separate chunk index, so the editor never sees WolvenKit’s HandleId numbers.

Bridge methods

The bridge holds the parsed file in memory for as long as the document is open. That is what makes partial reads and patches cheap: the 21 MB file is parsed once, in 0.8 s, and every call after that touches a few nodes.

Method Request Response Notes
cr2w.open file (disk path) or path (game path) doc id, root type, file size A game path opens read-only. Parses once
cr2w.getNode doc, path, offset?, limit? The node: its kind (class, array, handle, buffer, value), type name, value for leaves and small structs, and one entry per child with name or index, kind, type, and a one-line preview. Arrays return total and the requested window limit defaults to 200. A buffer returns its byte size only
cr2w.describeType type The class’s properties with their types and default values, enum values for enums, and the element type for arrays From WolvenKit’s reflection. Cached per bridge run. Drives the property grid and validation
cr2w.createInstance type A default instance as JSON For “add” on arrays and handles. Named in the architecture document
cr2w.patch doc, ops applied count and the inverse ops JSON Patch (RFC 6902) subset: replace, add, remove, move. Values are in WolvenKit JSON form, the same the text document shows. Applied atomically: all or none, with the error naming the failing op and path
cr2w.search doc, query, limit? Paths and previews of leaves whose value contains the query For the footer search and the table filter
cr2w.save doc, outFile? File path and bytes Runs the same writer as cr2w.fromJson, so WolvenKit’s pre-processors run (compiledData is rebuilt). Then the editor refreshes the nodes it shows
cr2w.close doc   Frees the parsed file

Numbers in ops are 64-bit doubles on the wire; the bridge converts to the field’s type and the inverse op carries the value as stored, so what undo restores is what the file held.

The VS Code side

Rules shown, not hidden

The three format rules found while testing Save JSON to Project become UI behaviour instead of a README table:

Rule In the editor
Numbers are 32-bit floats The number input shows the value the file will hold as soon as the edit is committed
compiledData is rebuilt from components Shown read-only with the note. The entity’s components subtree is where edits go
Properties at their default are not stored Invisible by design. The grid shows every property of the type with its default, because describeType knows the defaults

A fourth rule found at the same time, that an unknown property name makes WolvenKit reject the whole file, cannot happen in the editor because the grid only offers the type’s properties.

Numbers that matter

Measurement Value Consequence
Localization JSON 21 MB, 60,303 rows, 0.8 s to produce Page arrays. Never send the document whole
Mesh buffers 70% of the JSON Buffers are size-only nodes
Entity 79 KB Could be loaded whole, but the same paging code costs nothing here
Nodes per typical file 500 to 1,000, except localization The tree can keep every loaded node in memory on the page
Bridge parse 10 ms atlas, 50 ms mesh, 770 ms entity (first call in a run), 790 ms localization Parse once per open document

Milestones

Each is one or two commits and ends with a hand test on the owner’s files plus a smoke step.

Step Bridge Extension Test file
E1 open, getNode, patch, save, close, with a document session table and a smoke step that patches a value and reads it back Client wrappers only .ent
E2 describeType, createInstance The custom editor: tree, property grid, undo and redo, save, backup and replay .ent and .app, then .mesh
E3 search, getNodes, inline values for small array elements Footer search. The table view. CR2W detection for .json and .csv by magic onscreens.json, then a subtitles file
E4   The atlas view, click to select .inkatlas
E5   Texture properties beside the viewer. The Properties sidebar view. Read-only open from Game Assets .xbm

Bridge 0.5.0 carries E1 and E2 and MinBridgeVersion moves to 0.5.0 when the editor ships, since the editor cannot work without them.

Deferred

Decisions

Agreed with the owner on 2026-10-12:

  1. .app appearance files are in the first test list with .ent. They share the compiledData rule and the components tree.
  2. The table view opens subtitle files as well as on-screens from the start. The only difference is the key columns, which come from the row type.
  3. The atlas view ships with click-to-select and numeric editing of the rectangle. Dragging edges is deferred.

Found during E3, 2026-10-12: after the bridge parses an entity, a component and its chunk in compiledData are the same object, so a patch through either path changes both. The JSON round trip behaves differently because deserialising makes separate objects. The editor keeps compiledData read-only so every edit is made once, and its banner says that the block mirrors components. The search walks both paths and reports both, with the compiledData hit dimmed.

Found during E4, 2026-10-12: the game’s atlases leave clippingRectInPixels at zero and carry the part only in clippingRectInUVCoords, so the UV rectangle is what the game reads. The atlas view draws from UV times the texture size, shows the rectangle in pixels for editing, and writes both rectangles in one patch so they agree afterwards. Slot 0 holds the 4K texture and slot 1 the _1080p one; slot 2 is empty in the files checked. The view shows one slot at a time, starting on the first one with parts, and offers the file’s root texture and parts as a last slot when an older file uses them. Game textures are stored bottom-up and WolvenKit flips them vertically on export and in its viewers, as the game does when sampling; REDbridge 0.5.0 saved them as stored, so every PNG was upside down and the atlas rectangles sat on the wrong rows. REDbridge 0.5.1 flips, LUTs excepted. Test with basegameplayguimmoniconsitemsitem_icons0.inkatlas, the current item icons; iconsitem_icons.inkatlas in basegame_1_engine is an older leftover.