-
Notifications
You must be signed in to change notification settings - Fork 0
Reliquary Developer Architecture
Technical architecture for Reliquary (placeable blueprint editor, .utp).
Reliquary edits Aurora Engine placeable blueprints (UTP). External name Reliquary, internal namespace PlaceableEditor, assembly Reliquary. Sister tool to Relique (same single-resource blueprint pattern). Epic #2289; design spec NonPublic/PlaceableEditor/2026-05-28-reliquary-design.md.
flowchart LR
MW[MainWindow partials] --> PVM[PlaceableViewModel]
PVM --> UTP[(UtpFile)]
MW --> ICP[IdentityCombatPanel]
MW --> BP[BehaviorPanel]
MW --> TP[TextPanel]
MW --> IP[InventoryPanel]
MW --> PB[PlaceableBrowserPanel]
MW --> Undo[UndoRedoManager]
MW --> Doc[DocumentState]
MainWindow is split into partials by concern (QM/Relique pattern): .axaml.cs (construction, keyboard shortcuts), .Lifecycle.cs (browser wiring, collapse, file-select), .Editor.cs (load/bind/save), .Document.cs (dirty tracking, save prompts), .Inventory.cs (palette + backpack + add/remove), .Services.cs (game data, appearance, model preview).
| Panel | Binds | Notes |
|---|---|---|
IdentityCombatPanel |
name/tag/resref/appearance, combat stats, flags | 3D model preview (fixed-square Border so the GL control gets real bounds, #2375); Static/Plot disable combat fields; portrait Browse → shared PortraitBrowserWindow (#2370); single "= Name" checkbox syncs Tag (UPPER) + ResRef (lower ≤16) via PlaceableNamingService (#2372) |
BehaviorPanel |
13 event scripts (2 columns), advanced, shared VariablesPanel
|
Browse/Edit per script slot; Save/Load Script Set presets; Faction combo (blank until picked) + Conversation Browse + Initial State named-dropdown; events raised to host |
TextPanel |
Description (TLK) + Comment | shared SpellCheckTextBox; Description has right-click token insert (#2075) |
InventoryPanel |
backpack + UTI palette + read-only details | visible only when HasInventory; undoable add/remove |
PlaceableBrowserPanel |
.utp list (Module/HAK/BIF) |
FileBrowserPanelBase subclass; Name/Tag indexing + read-only archive preview |
sequenceDiagram
participant B as Browser/Open
participant MW as MainWindow.Editor
participant R as UtpReader
participant VM as PlaceableViewModel
participant P as Panels
B->>MW: file path / archive bytes
MW->>MW: _isLoading = true
MW->>R: Read(path|bytes)
R-->>VM: new PlaceableViewModel(UtpFile)
MW->>P: BindPlaceable (DataContext on each panel)
MW->>VM: TrackPlaceableEdits (PropertyChanged → MarkDirty)
MW->>MW: undo.Clear + ClearDirty + _isLoading = false
Archive (BIF/HAK) rows have no file path: the host extracts bytes via PlaceableBrowserPanel.ExtractArchiveBytes, loads read-only (DocumentState.IsReadOnly = true, CurrentFilePath = null). Save on a read-only/never-saved document routes to Save As, which copies the placeable into the module.
A startup --file is opened from MainWindow.Services.OnWindowOpened (after game-data/preview services stand up), mirroring Relique. LoadPlaceable/LoadPlaceableFromBytes call UpdateTitle() explicitly after binding: BindPlaceable's ClearDirty is a no-op when the document is already clean, so the title would otherwise never pick up the new CurrentFilePath (#2297).
File → New (Ctrl+N) binds PlaceableViewModel.NewPlaceable() — a blank, immediately round-trippable UtpFile (Useable, no inventory) — as an unsaved document; first Save routes through Save As (#2367). Recent Files persist via the inherited BaseToolSettingsService MRU (ReliquarySettings.json); AddRecentFile fires on every load/save, the Open Recent submenu repopulates, and Trebuchet reads the list through ToolRecentFilesService (Reliquary registered in GetSettingsPathFor) (#2368).
BehaviorPanel raises SaveScriptSetRequested / LoadScriptSetRequested; the host file-picks a .txt preset and round-trips it through the pure ScriptSetService (EventName=ResRef lines, tolerates blank/#/whitespace). Load applies as one RelayUndoableCommand step and clears slots the preset omits (#2369, #2374).
BehaviorPanel raises EditConversationRequested (Edit → Parley) and ConversationBrowseRequested (Browse…, #2373). Edit resolves the Conversation ResRef to a .dlg near the file/module via ExternalEditorService.ResolveResourcePath and launches Parley through the shared ToolDispatchService (ResourceTypes.Dlg); Browse opens the shared DialogBrowserWindow and routes the selection through undo. .utp is registered in ToolDispatchService so other tools can dispatch placeables back to Reliquary. (Tool-path discovery is settings-first as of #2377 — see Radoub-UI-Developer.)
FactionService.Load(moduleDir) reads the module's repute.fac via shared FacReader into (Id, Name) pairs, with the five standard NWN factions as fallback (no hardcoded faction data when a module file is present, #2354). The Behavior panel's Faction combo starts blank — the user opens the dropdown to assign — and routes the pick through SetFieldCommand<uint>. Initial State is a named dropdown backed by PlaceableAnimationState.All (the six engine-fixed states from BioWare Door/Placeable GFF Table 4.1.2: Default/Open/Closed/Destroyed/Activated/Deactivated = 0–5), writing the byte to UtpFile.AnimationState (#2376).
Two edit paths feed one dirty flag (guarded by _isLoading):
- Binding edits (identity/text fields) → VM
PropertyChanged→MarkDirty - Command edits (appearance/scripts/variables/inventory) →
UndoRedoManager.StateChanged→MarkDirty
Open / browser-select / window-close gate on _isDirty and prompt Save / Don't Save / Cancel.
flowchart LR
Cache[(ItemPalette cache)] --> Agg[GetAggregatedCache]
Build[BuildItemCacheAsync] --> Cache
GD[GameDataService] --> Build
HAK[HakPaletteScannerService] --> Cache
Agg --> VMs[ItemViewModel list]
Mod[module loose .uti] --> VMs
VMs --> Filter[ItemFilterPanel]
Filter --> List[ItemListView]
First inventory use builds missing BIF/Override source caches + scans module HAKs (shared cache at ~/Radoub/Cache/ItemPalette), then aggregates and adds loose module .uti. ItemFilterPanel.ShowCustom = true so module/Override/HAK items show alongside BIF. Add/Remove are AddInventoryItemCommand/RemoveInventoryItemCommand (undoable); BIF UTIs with empty TemplateResRef get the resource ResRef stamped so InventoryRes round-trips.
-
PlaceableViewModel— two-way facade overUtpFile(model is single source of truth, no shadow copy). Derived enablement:IsCombatEnabled(!Static),IsDamageEnabled(!Static && !Plot). -
PlaceableAppearanceService(Radoub.Formats) — placeables.2da → model/display name. -
PlaceableModelLoader+TextureService— 3D preview MDL load. -
ItemViewModelFactory(Radoub.UI) — UTI → ItemViewModel + cache display helpers. -
ItemResolution(MainWindow.Inventory) — UTI cascade module → Override → HAK → BIF. -
ScriptSetService— pure serialize/parse/apply of the 13-slot script preset (.txt). -
ReliquaryPortraitBrowserContext—IPortraitBrowserContextoverGameDataService+ItemIconService(portraits.2da; mirrors QM).
UtpFile (Radoub.Formats.Utp): identity, combat, flags, 13 scripts, ItemList (PlaceableItem = InventoryRes + grid position only — no per-instance stack/charges), VarTable. Parser/writer: UtpReader/UtpWriter. See Radoub-Formats-UTP.
Reliquary.Tests: CommandLineService, SettingsService, PlaceableViewModel (incl. NewPlaceable factory + round-trip), round-trip, undo/redo commands (incl. inventory add/remove), ScriptSetServiceTests (txt serialize/parse/apply), ReliquaryPortraitBrowserContextTests (asterisk-pad skip), PlaceableBrowserPanelIndexingTests (ReadUtpMetadata + TryFillFromCache). Radoub.IntegrationTests/Reliquary/ReliquarySmokeTests (FlaUI): launch, --file load + Name field, HasInventory toggle reveals inventory panel, Ctrl+S clears dirty, graceful close. Reliquary is in run-tests.ps1 (-Tool + UI map).
Sprints 4-7 complete: scaffolding; IdentityCombat + Behavior; Text + Inventory + browser metadata; cross-tool dispatch (Conversation → Parley) + FlaUI smoke + UI uniformity audit (12/12). Post-epic audit (PR #2371) added New flow, Recent Files/MRU, script-set presets, and portrait Browse. Epic follow-ups (PR #2377) closed the remaining gaps: faction combo from repute.fac (#2354), resizable window + inventory pane fixes + F4 browser collapse (#2363), Tag/ResRef name-sync (#2372), conversation Browse (#2373), model-preview fit (#2375), and Initial State named dropdown (#2376).
Page freshness: 2026-06-06
Getting Started
User Guide
Features
Help
- Manifest - Journal Editor
- Quartermaster - Creature/Inventory Editor
- Relique - Item Editor
- Reliquary - Placeable Editor (Alpha)
- Fence - Merchant/Store Editor
- Trebuchet - Radoub Launcher
- Marlinspike - Search and Replace
- Spell Check - Dictionary-based spell checking
- Token System - Dialog tokens and custom colors
Parley Internals
Manifest Internals
Quartermaster Internals
Relique Internals
Reliquary Internals
Fence Internals
Marlinspike (Search Engine)
Trebuchet Internals
Radoub.UI
Library
Low-Level Formats
High-Level Parsers
- JRL Format (.jrl)
- UTI Format (.uti) - Item blueprints
- UTC Format (.utc) - Creature blueprints
- UTM Format (.utm) - Store blueprints
- UTP Format (.utp) - Placeable blueprints
- UTD Format (.utd) - Door blueprints
- ARE Format (.are) - Area properties
- BIC Format (.bic) - Player characters
Original BioWare Aurora Engine file format specifications.
Core Formats
- GFF Format - Generic File Format
- KEY/BIF Format - Resource archives
- ERF Format - Encapsulated resources
- TLK Format - Talk tables
- 2DA Format - Data tables
- Localized Strings
- Common GFF Structs
Object Blueprints
- Creature Format (.utc)
- Item Format (.uti)
- Store Format (.utm)
- Door/Placeable (.utd/.utp)
- Encounter Format (.ute)
- Sound Object (.uts)
- Trigger Format (.utt)
- Waypoint Format (.utw)
Module/Area Files
- Conversation Format (.dlg)
- Journal Format (.jrl)
- Area File Format (.are/.git/.gic)
- Module Info (.ifo)
- Faction Format (.fac)
- Palette/ITP Format (.itp)
- SSF Format - Sound sets
Reference
Page freshness: 2026-05-24