Skip to content

Reliquary Developer Architecture

LordOfMyatar edited this page Jun 7, 2026 · 2 revisions

Reliquary-Developer-Architecture

Technical architecture for Reliquary (placeable blueprint editor, .utp).

Overview

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.

Component Structure

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]
Loading

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

Panels

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

Data Flow

Load

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
Loading

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

New / Open / Recent

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

Script sets

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

Conversation dispatch (→ Parley)

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

Faction + animation-state catalogs

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

Dirty tracking

Two edit paths feed one dirty flag (guarded by _isLoading):

  • Binding edits (identity/text fields) → VM PropertyChangedMarkDirty
  • Command edits (appearance/scripts/variables/inventory) → UndoRedoManager.StateChangedMarkDirty

Open / browser-select / window-close gate on _isDirty and prompt Save / Don't Save / Cancel.

Inventory palette

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]
Loading

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.

Key Services

  • PlaceableViewModel — two-way facade over UtpFile (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).
  • ReliquaryPortraitBrowserContextIPortraitBrowserContext over GameDataService + ItemIconService (portraits.2da; mirrors QM).

Models

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.

Tests

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

Status

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


Parley

Getting Started

User Guide

Features

Help


Manifest


Quartermaster


Relique


Reliquary


Fence

  • Fence - Merchant/Store Editor

Trebuchet


Shared Features


Developers

Parley Internals

Manifest Internals

Quartermaster Internals

Relique Internals

Reliquary Internals

Fence Internals

Marlinspike (Search Engine)

Trebuchet Internals

Radoub.UI


Radoub.Formats

Library

Low-Level Formats

High-Level Parsers


Legacy Bioware Docs

Original BioWare Aurora Engine file format specifications.

Core Formats

Object Blueprints

Module/Area Files

Reference


Page freshness: 2026-05-24

Index

Clone this wiki locally