675 lines
26 KiB
Markdown
675 lines
26 KiB
Markdown
# Artificer's Scrollwork — CLAUDE.md
|
||
|
||
> Local UO development workbench for ServUO shard developers.
|
||
> Combines script analysis, asset reference, Gump rendering, and interactive flow tracing.
|
||
> Built with Tauri (Rust shell) + React frontend + C# sidecar.
|
||
|
||
---
|
||
|
||
## Project Identity
|
||
|
||
**Name:** Artificer's Scrollwork
|
||
**Short name / slug:** artificers-scrollwork
|
||
**Abbreviation:** ASW
|
||
**License:** Open source (MIT)
|
||
**Platform:** Windows desktop (Tauri)
|
||
**Target user:** ServUO shard developer working locally — no live shard connection required
|
||
|
||
---
|
||
|
||
## Repository Structure
|
||
|
||
```
|
||
artificers-scrollwork/
|
||
├── CLAUDE.md ← this file, always read first
|
||
├── src-tauri/ ← Rust/Tauri backend
|
||
│ ├── src/
|
||
│ │ ├── main.rs
|
||
│ │ ├── config.rs ← path config, validation
|
||
│ │ ├── db.rs ← SQLite via rusqlite
|
||
│ │ ├── assets/
|
||
│ │ │ ├── mod.rs
|
||
│ │ │ ├── mul_reader.rs ← .mul file parser
|
||
│ │ │ ├── uop_reader.rs ← .uop file parser
|
||
│ │ │ ├── art.rs ← art.mul / artLegacyMUL.uop
|
||
│ │ │ ├── gumpart.rs ← gumpart.mul / gumpartLegacyMUL.uop
|
||
│ │ │ ├── tiledata.rs ← tiledata.mul
|
||
│ │ │ ├── hues.rs ← hues.mul
|
||
│ │ │ ├── cliloc.rs ← cliloc.enu string table
|
||
│ │ │ ├── anim.rs ← animX.mul mobile sprites
|
||
│ │ │ └── multi.rs ← multi.mul structures
|
||
│ │ ├── ipc/
|
||
│ │ │ ├── mod.rs
|
||
│ │ │ └── sidecar.rs ← C# sidecar process management
|
||
│ │ └── commands/ ← Tauri command handlers (IPC to frontend)
|
||
│ │ ├── config_commands.rs
|
||
│ │ ├── asset_commands.rs
|
||
│ │ ├── script_commands.rs
|
||
│ │ └── gump_commands.rs
|
||
│ └── Cargo.toml
|
||
├── sidecar/ ← C# Roslyn sidecar
|
||
│ ├── ArtificersScrollwork.Sidecar.csproj
|
||
│ ├── Program.cs ← stdin/stdout JSON IPC entry point
|
||
│ ├── Parsing/
|
||
│ │ ├── ScriptIndexer.cs ← walks Scripts/ tree, indexes .cs files
|
||
│ │ ├── RoslynParser.cs ← Roslyn AST parsing
|
||
│ │ ├── ClassInfo.cs ← data model: class, base, props, methods
|
||
│ │ ├── MethodInfo.cs
|
||
│ │ └── CallChainTracer.cs ← traces method call chains across files
|
||
│ ├── Gumps/
|
||
│ │ ├── GumpExtractor.cs ← extracts Add* calls from Gump constructors
|
||
│ │ └── GumpDrawList.cs ← serializable draw call list → JSON
|
||
│ └── Models/
|
||
│ ├── ScriptIndex.cs
|
||
│ ├── DrawCall.cs
|
||
│ └── FlowNode.cs
|
||
├── src/ ← React frontend
|
||
│ ├── main.tsx
|
||
│ ├── App.tsx
|
||
│ ├── components/
|
||
│ │ ├── layout/
|
||
│ │ │ ├── AppShell.tsx ← three-pane shell
|
||
│ │ │ ├── LeftPanel.tsx ← browser tree
|
||
│ │ │ ├── CenterPanel.tsx ← flow / asset / gump view
|
||
│ │ │ └── RightPanel.tsx ← properties / fake data inputs
|
||
│ │ ├── asset/
|
||
│ │ │ ├── StaticBrowser.tsx ← static tile browser
|
||
│ │ │ ├── MobileBrowser.tsx ← mobile browser
|
||
│ │ │ ├── ItemPreview.tsx ← single item art + metadata
|
||
│ │ │ └── MobilePreview.tsx ← single mobile sprite
|
||
│ │ ├── script/
|
||
│ │ │ ├── ScriptTree.tsx ← class/method/hook tree
|
||
│ │ │ ├── ClassDetail.tsx ← class info panel
|
||
│ │ │ └── MethodDetail.tsx ← method info panel
|
||
│ │ ├── flow/
|
||
│ │ │ ├── FlowViewer.tsx ← call chain flow diagram
|
||
│ │ │ ├── FlowNode.tsx ← individual node in flow
|
||
│ │ │ └── FakeDataInputs.tsx ← right panel fake data widgets
|
||
│ │ ├── gump/
|
||
│ │ │ ├── GumpRenderer.tsx ← 800x600 canvas Gump renderer
|
||
│ │ │ └── GumpCanvas.tsx ← raw canvas draw call executor
|
||
│ │ └── config/
|
||
│ │ └── ConfigScreen.tsx ← UO root + ServUO Scripts path setup
|
||
│ ├── hooks/
|
||
│ │ ├── useAssets.ts
|
||
│ │ ├── useScripts.ts
|
||
│ │ └── useGump.ts
|
||
│ ├── store/
|
||
│ │ └── appStore.ts ← Zustand global state
|
||
│ └── types/
|
||
│ ├── assets.ts
|
||
│ ├── scripts.ts
|
||
│ ├── gump.ts
|
||
│ └── flow.ts
|
||
├── tauri.conf.json
|
||
├── package.json
|
||
└── README.md
|
||
```
|
||
|
||
---
|
||
|
||
## Architecture
|
||
|
||
```
|
||
┌─────────────────────────────────────────────┐
|
||
│ React Frontend │
|
||
│ LeftPanel | CenterPanel | RightPanel │
|
||
│ Asset Browser | Flow Viewer | Gump Renderer │
|
||
└────────────────────┬────────────────────────┘
|
||
│ Tauri invoke() IPC
|
||
┌────────────────────▼────────────────────────┐
|
||
│ Rust / Tauri Core │
|
||
│ config.rs | db.rs | asset parsers │
|
||
│ mul/uop readers | SQLite index │
|
||
└────────────────────┬────────────────────────┘
|
||
│ stdin/stdout JSON IPC
|
||
┌────────────────────▼────────────────────────┐
|
||
│ C# Roslyn Sidecar │
|
||
│ ScriptIndexer | RoslynParser │
|
||
│ CallChainTracer | GumpExtractor │
|
||
└─────────────────────────────────────────────┘
|
||
```
|
||
|
||
### IPC Protocol — Rust ↔ C# Sidecar
|
||
|
||
All messages are newline-delimited JSON over stdin/stdout.
|
||
|
||
**Request format:**
|
||
```json
|
||
{ "id": "uuid", "command": "parse_class", "args": { "file": "path/to/Script.cs", "class": "PaperDollGump" } }
|
||
```
|
||
|
||
**Response format:**
|
||
```json
|
||
{ "id": "uuid", "ok": true, "data": { ... } }
|
||
{ "id": "uuid", "ok": false, "error": "message" }
|
||
```
|
||
|
||
**Commands:**
|
||
- `index_scripts` — walk Scripts/ tree, build full index
|
||
- `get_class` — return ClassInfo for a named class
|
||
- `trace_method` — return FlowNode tree for a method entry point
|
||
- `extract_gump` — return GumpDrawList for a Gump class constructor
|
||
- `search` — full-text search across indexed scripts
|
||
|
||
---
|
||
|
||
## Configuration
|
||
|
||
Stored in SQLite `config` table (key/value).
|
||
|
||
| Key | Description |
|
||
|---|---|
|
||
| `uo_root` | Absolute path to UO client folder |
|
||
| `seruo_scripts` | Absolute path to ServUO Scripts/ folder |
|
||
| `index_version` | Hash/timestamp of last script index |
|
||
| `asset_format` | `mul` or `uop` (autodetected) |
|
||
|
||
### Path Validation
|
||
|
||
On config save, validate:
|
||
|
||
**UO Root — required files:**
|
||
```
|
||
art.mul OR artLegacyMUL.uop
|
||
gumpart.mul OR gumpartLegacyMUL.uop
|
||
tiledata.mul
|
||
hues.mul
|
||
cliloc.enu
|
||
unifont.mul
|
||
```
|
||
|
||
**UO Root — optional (warn if missing):**
|
||
```
|
||
anim.mul / animX.mul → mobile animations
|
||
multi.mul → structures
|
||
map0.mul → world map
|
||
radarcol.mul → minimap colors
|
||
```
|
||
|
||
**ServUO Scripts — required:**
|
||
```
|
||
<path>/ must exist and contain at least one .cs file
|
||
```
|
||
|
||
Report validation results per-file with ✅ / ⚠️ / ❌ status.
|
||
|
||
---
|
||
|
||
## Data Models
|
||
|
||
### Asset Types
|
||
|
||
```typescript
|
||
// types/assets.ts
|
||
|
||
interface TileInfo {
|
||
id: number; // item ID / static ID
|
||
name: string; // from tiledata.mul
|
||
flags: number; // tiledata flags bitmask
|
||
weight: number;
|
||
quality: number;
|
||
height: number;
|
||
hue: number;
|
||
artData: ImageData; // decoded pixel data
|
||
}
|
||
|
||
interface MobileInfo {
|
||
bodyId: number;
|
||
name: string;
|
||
flags: number;
|
||
frames: ImageData[]; // animation frames
|
||
}
|
||
|
||
interface HueInfo {
|
||
id: number;
|
||
name: string;
|
||
colors: number[]; // 32-entry color table
|
||
}
|
||
```
|
||
|
||
### Script Types
|
||
|
||
```typescript
|
||
// types/scripts.ts
|
||
|
||
interface ClassInfo {
|
||
name: string;
|
||
namespace: string;
|
||
filePath: string;
|
||
baseClass: string | null;
|
||
interfaces: string[];
|
||
properties: PropertyInfo[];
|
||
methods: MethodInfo[];
|
||
attributes: string[];
|
||
isGump: boolean;
|
||
isMobile: boolean;
|
||
isItem: boolean;
|
||
}
|
||
|
||
interface MethodInfo {
|
||
name: string;
|
||
returnType: string;
|
||
parameters: ParameterInfo[];
|
||
isOverride: boolean;
|
||
isVirtual: boolean;
|
||
callsGump: boolean; // does this method send a Gump?
|
||
gumpClass: string | null; // which Gump class if so
|
||
}
|
||
|
||
interface FlowNode {
|
||
id: string;
|
||
type: 'method_call' | 'condition' | 'gump_send' | 'return' | 'property_access';
|
||
label: string;
|
||
children: FlowNode[];
|
||
fakeInputKey?: string; // key for fake data injection if this node has an input
|
||
resolvedGump?: string; // Gump class name if type === 'gump_send'
|
||
assetRef?: number; // item/static ID if this node refs an asset
|
||
}
|
||
```
|
||
|
||
### Gump Types
|
||
|
||
```typescript
|
||
// types/gump.ts
|
||
|
||
type DrawCall =
|
||
| { type: 'background'; x: number; y: number; w: number; h: number; gumpId: number }
|
||
| { type: 'image'; x: number; y: number; gumpId: number; hue?: number }
|
||
| { type: 'label'; x: number; y: number; hue: number; text: string }
|
||
| { type: 'button'; x: number; y: number; normalId: number; pressedId: number; buttonId: number }
|
||
| { type: 'html'; x: number; y: number; w: number; h: number; text: string; hasBackground: boolean; hasScrollbar: boolean }
|
||
| { type: 'item'; x: number; y: number; itemId: number; hue?: number }
|
||
| { type: 'alpha_region'; x: number; y: number; w: number; h: number }
|
||
| { type: 'tiled_image'; x: number; y: number; w: number; h: number; gumpId: number }
|
||
| { type: 'checkbox'; x: number; y: number; inactiveId: number; activeId: number; checked: boolean; switchId: number }
|
||
| { type: 'radio'; x: number; y: number; inactiveId: number; activeId: number; checked: boolean; returnValue: number }
|
||
| { type: 'text_entry'; x: number; y: number; w: number; h: number; hue: number; entryId: number; initialText: string };
|
||
|
||
interface GumpDrawList {
|
||
className: string;
|
||
filePath: string;
|
||
isDynamic: boolean; // true if layout depends on runtime data
|
||
dynamicInputKeys: string[]; // fake data keys needed for dynamic Gumps
|
||
drawCalls: DrawCall[];
|
||
width: number; // declared width if available
|
||
height: number; // declared height if available
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## UI Layout
|
||
|
||
### Three-Pane Shell
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────────────────┐
|
||
│ Artificer's Scrollwork [Config] [Index] │
|
||
├─────────────────┬──────────────────────────┬─────────────────────┤
|
||
│ LEFT PANEL │ CENTER PANEL │ RIGHT PANEL │
|
||
│ ~280px │ flex-grow │ ~320px │
|
||
│ │ │ │
|
||
│ [Scripts] │ ┌────────────────────┐ │ Properties │
|
||
│ [Statics] │ │ │ │ ───────────────── │
|
||
│ [Mobiles] │ │ Flow / Asset / │ │ Fake Data Inputs │
|
||
│ [Gumps] │ │ Gump View │ │ ───────────────── │
|
||
│ │ │ │ │ Asset Metadata │
|
||
│ Search box │ │ │ │ │
|
||
│ │ │ │ │ │
|
||
│ Tree │ └────────────────────┘ │ │
|
||
│ │ │ │
|
||
└─────────────────┴──────────────────────────┴─────────────────────┘
|
||
```
|
||
|
||
### Center Panel Modes
|
||
|
||
The center panel has distinct view modes, switched by what is selected in the left panel:
|
||
|
||
| Mode | Triggered By | Shows |
|
||
|---|---|---|
|
||
| `asset_static` | Static selected | Tile art + metadata |
|
||
| `asset_mobile` | Mobile selected | Mobile sprite + stats |
|
||
| `script_class` | Class selected | Class detail, inheritance tree |
|
||
| `flow_method` | Method/hook selected | Call chain flow diagram |
|
||
| `gump_render` | Gump class or method sends Gump | 800x600 Gump canvas |
|
||
|
||
### Gump Renderer
|
||
|
||
- Fixed 800x600 HTML canvas
|
||
- Dark border/frame to indicate viewport bounds
|
||
- Renders draw calls top-to-bottom in order
|
||
- Dynamic Gumps show placeholder tiles with fake data input widgets in right panel
|
||
- "Refresh Gump" button re-renders with current fake data values
|
||
|
||
---
|
||
|
||
## Asset Parsing — Rust Implementation Notes
|
||
|
||
### .mul Format
|
||
|
||
All .mul files share the same basic indexed format:
|
||
```
|
||
<name>idx.mul → index file: array of (offset: i32, length: i32, extra: i32) entries
|
||
<name>.mul → data file: raw blocks at offsets given by index
|
||
```
|
||
|
||
Entry with `offset == -1` → entry does not exist (skip).
|
||
|
||
### art.mul
|
||
|
||
- Statics (items): ID offset = 0x4000
|
||
- Each entry is a raw bitmap: `{ unknown: u16, width: u16, height: u16, lookup: [u16; height], data: [u16] }`
|
||
- Pixel format: 16-bit `0xARGB` (1-bit alpha, 5-bit RGB each)
|
||
- Transparent pixel = `0x0000`
|
||
|
||
### gumpart.mul
|
||
|
||
- Same index structure as art.mul
|
||
- Pixel format: same 16-bit `0xARGB`
|
||
- No offset for statics — IDs are direct
|
||
|
||
### tiledata.mul
|
||
|
||
- Land tiles: 428 blocks × 32 entries = 13,696 land entries
|
||
- Item tiles: blocks of 32 entries, each entry = `{ flags: u64, weight: u8, quality: u8, unknown: u16, unknown2: u8, quantity: u8, animId: u16, unknown3: u8, hue: u8, unknown4: u16, height: u8, name: [u8; 20] }`
|
||
- Block header = `u32` (skip it)
|
||
|
||
### hues.mul
|
||
|
||
- 375 blocks × 8 hues = 3,000 hues
|
||
- Each hue = `{ colors: [u16; 32], tableStart: u16, tableEnd: u16, name: [u8; 20] }`
|
||
|
||
### .uop Format
|
||
|
||
UOP is a container format used in newer clients:
|
||
```
|
||
Header: { magic: u32=0x0050594D, version: u32, signature: u32, index_offset: u64, max_files: u32, tag: [u8; 36] }
|
||
Index block: { next_block: u64, file_count: u32, entries: [UOPEntry; file_count] }
|
||
UOPEntry: { data_offset: u64, header_length: u32, compressed_length: u32, decompressed_length: u32, hash: u64, crc: u32, compression: u16 }
|
||
```
|
||
|
||
Compression: `0` = none, `1` = zlib deflate.
|
||
|
||
Hash function for UOP filenames (use to map asset IDs to UOP entries):
|
||
```rust
|
||
fn uop_hash(s: &str) -> u64 {
|
||
let s = s.to_lowercase();
|
||
let (mut eax, mut ecx, mut edx, mut ebx, mut esi, mut edi) =
|
||
(0u32, 0u32, 0u32, s.len() as u32, 0u32, 0u32);
|
||
edx = 0; eax = edx; esi = eax;
|
||
ecx = 0x9E3779B9u32;
|
||
edi = ecx; esi = ecx;
|
||
for chunk in s.as_bytes().chunks(12) {
|
||
// standard UOP hash — implement from ClassicUO source
|
||
}
|
||
((edi as u64) << 32) | (esi as u64)
|
||
}
|
||
```
|
||
|
||
Reference ClassicUO `UOFileUop.cs` for the full implementation.
|
||
|
||
---
|
||
|
||
## C# Sidecar — Implementation Notes
|
||
|
||
### Project Setup
|
||
|
||
```xml
|
||
<Project Sdk="Microsoft.NET.Sdk">
|
||
<PropertyGroup>
|
||
<OutputType>Exe</OutputType>
|
||
<TargetFramework>net8.0</TargetFramework>
|
||
<Nullable>enable</Nullable>
|
||
<ImplicitUsings>enable</ImplicitUsings>
|
||
<PublishSingleFile>true</PublishSingleFile>
|
||
<SelfContained>true</SelfContained>
|
||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||
</PropertyGroup>
|
||
<ItemGroup>
|
||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.*" />
|
||
<PackageReference Include="System.Text.Json" Version="8.*" />
|
||
</ItemGroup>
|
||
</Project>
|
||
```
|
||
|
||
### Roslyn Parser Notes
|
||
|
||
- Load all `.cs` files in Scripts/ into a `CSharpCompilation` for cross-file resolution
|
||
- Use `SemanticModel` for type resolution (base classes, method return types)
|
||
- `SyntaxTree` for structural traversal (Add* call extraction)
|
||
- Do NOT attempt to actually compile ServUO — use `WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))` with missing reference tolerance
|
||
|
||
### GumpExtractor — Add* Call Mapping
|
||
|
||
| C# Method | DrawCall type |
|
||
|---|---|
|
||
| `AddBackground(x, y, w, h, id)` | `background` |
|
||
| `AddImage(x, y, id)` | `image` |
|
||
| `AddImage(x, y, id, hue)` | `image` with hue |
|
||
| `AddLabel(x, y, hue, text)` | `label` |
|
||
| `AddHtml(x, y, w, h, text, bg, scroll)` | `html` |
|
||
| `AddHtmlLocalized(x, y, w, h, clilocId, ...)` | `html` (resolve via cliloc) |
|
||
| `AddButton(x, y, normalId, pressedId, btnId, ...)` | `button` |
|
||
| `AddItem(x, y, itemId)` | `item` |
|
||
| `AddItem(x, y, itemId, hue)` | `item` with hue |
|
||
| `AddAlphaRegion(x, y, w, h)` | `alpha_region` |
|
||
| `AddImageTiled(x, y, w, h, id)` | `tiled_image` |
|
||
| `AddCheck(x, y, inactId, actId, checked, switchId)` | `checkbox` |
|
||
| `AddRadio(x, y, inactId, actId, checked, returnVal)` | `radio` |
|
||
| `AddTextEntry(x, y, w, h, hue, entryId, text)` | `text_entry` |
|
||
|
||
For dynamic Gumps (loop bodies, conditional Add* calls):
|
||
- Mark `isDynamic: true`
|
||
- Record the variable names that gate the dynamic calls as `dynamicInputKeys`
|
||
- Extract the static portions and emit placeholder draw calls for dynamic regions
|
||
|
||
### CallChainTracer Notes
|
||
|
||
- Entry point: a method name + class name
|
||
- Resolve method body via Roslyn
|
||
- Walk all `InvocationExpressionSyntax` nodes
|
||
- For each invocation:
|
||
- If `SendGump(new XxxGump(...))` → emit `gump_send` node, recurse into Gump constructor
|
||
- If calling another method in the same class → recurse
|
||
- If calling a method on a known ServUO base class → emit as leaf with label
|
||
- If an `if`/`else`/`switch` → emit `condition` node with branches
|
||
- Limit recursion depth to 8 to avoid infinite loops
|
||
|
||
---
|
||
|
||
## Build & Development
|
||
|
||
### Prerequisites
|
||
|
||
- Rust stable (latest)
|
||
- Node.js 20+
|
||
- .NET 8 SDK
|
||
- Tauri CLI: `cargo install tauri-cli`
|
||
|
||
### Development Commands
|
||
|
||
```bash
|
||
# Install JS deps
|
||
npm install
|
||
|
||
# Build C# sidecar
|
||
cd sidecar && dotnet publish -c Release -r win-x64 --self-contained
|
||
|
||
# Copy sidecar binary to Tauri resources
|
||
cp sidecar/bin/Release/net8.0/win-x64/publish/asw-sidecar.exe src-tauri/binaries/
|
||
|
||
# Run in dev mode
|
||
npm run tauri dev
|
||
|
||
# Build release
|
||
npm run tauri build
|
||
```
|
||
|
||
### Tauri Sidecar Config
|
||
|
||
In `tauri.conf.json`:
|
||
```json
|
||
{
|
||
"tauri": {
|
||
"bundle": {
|
||
"externalBin": ["binaries/asw-sidecar"]
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Development Phases
|
||
|
||
### Phase 1 — Foundation & Asset Reference
|
||
**Goal:** Working app that can browse UO assets
|
||
|
||
- [ ] Tauri project scaffold with three-pane React shell
|
||
- [ ] Config screen — UO root + ServUO Scripts path, validation
|
||
- [ ] SQLite setup — config table, asset index tables
|
||
- [ ] Rust mul/uop parsers — art, gumpart, tiledata, hues, cliloc
|
||
- [ ] Asset indexer — walk tiledata, build SQLite records for all statics and mobiles
|
||
- [ ] Static browser — paginated grid with art preview and metadata
|
||
- [ ] Mobile browser — list with sprite preview
|
||
- [ ] Item/tile detail view — full metadata panel
|
||
|
||
**Deliverable:** Browse every static and mobile in the UO client with art previews.
|
||
|
||
---
|
||
|
||
### Phase 2 — Script Browser
|
||
**Goal:** Ingest and navigate ServUO scripts
|
||
|
||
- [ ] C# sidecar project setup with Roslyn
|
||
- [ ] ScriptIndexer — walk Scripts/ tree, parse all .cs files, emit ClassInfo JSON
|
||
- [ ] Rust sidecar IPC layer — spawn sidecar, send/receive JSON commands
|
||
- [ ] SQLite script index — classes, methods, properties, file paths
|
||
- [ ] Re-index command with progress indicator
|
||
- [ ] Left panel script tree — namespaces → classes → methods
|
||
- [ ] Class detail view — inheritance chain, properties, methods list
|
||
- [ ] Method detail view — parameters, return type, attributes
|
||
- [ ] Search — cross-script full-text search
|
||
|
||
**Deliverable:** Browse the entire ServUO Scripts/ tree with structured class and method views.
|
||
|
||
---
|
||
|
||
### Phase 3 — Flow Tracer
|
||
**Goal:** Visualize call chains from any method entry point
|
||
|
||
- [ ] CallChainTracer in C# sidecar
|
||
- [ ] FlowNode JSON serialization
|
||
- [ ] FlowViewer React component — tree/graph layout of call chain
|
||
- [ ] Condition nodes with true/false branches
|
||
- [ ] Asset reference inline — when flow references an Item ID, show thumbnail
|
||
- [ ] Hook index — surface well-known hooks (OnDoubleClick, OnSingleClick, OnDeath, etc.) as quick-launch entry points
|
||
- [ ] Fake data input system — right panel widgets for condition inputs
|
||
- [ ] Flow re-evaluation when fake data changes
|
||
|
||
**Deliverable:** Click any method or hook, see the full call chain with fake data controls.
|
||
|
||
---
|
||
|
||
### Phase 4 — Gump Renderer
|
||
**Goal:** Render any Gump class on an 800x600 canvas
|
||
|
||
- [ ] GumpExtractor in C# sidecar
|
||
- [ ] GumpDrawList JSON serialization
|
||
- [ ] GumpCanvas React component — 800x600 HTML canvas
|
||
- [ ] Draw call executor — background, image, label, button, item, alpha, tiled, html, checkbox, radio, text entry
|
||
- [ ] gumpart.mul → ImageData conversion in Rust, served via Tauri command
|
||
- [ ] Hue application pipeline
|
||
- [ ] Static Gump rendering — fully static Add* calls
|
||
- [ ] Dynamic Gump rendering — fake data inputs drive dynamic draw call generation
|
||
- [ ] Gump link from flow tracer — gump_send nodes in flow open Gump renderer inline
|
||
|
||
**Deliverable:** Any Gump class renders visually. Dynamic Gumps render with fake data.
|
||
|
||
---
|
||
|
||
## Visual Design
|
||
|
||
### Theme
|
||
|
||
Dark theme. UO-adjacent aesthetic — aged parchment for text, deep slate/obsidian backgrounds, gold/amber accents. Feels like a scholar's workbench, not a generic dev tool.
|
||
|
||
**Color palette (CSS variables):**
|
||
```css
|
||
--bg-base: #0e0d0b;
|
||
--bg-panel: #161410;
|
||
--bg-elevated: #1e1b16;
|
||
--bg-hover: #272318;
|
||
--border: #3a3020;
|
||
--border-accent: #6b5a2e;
|
||
--text-primary: #d4c49a;
|
||
--text-secondary: #8a7a5a;
|
||
--text-muted: #4a4030;
|
||
--accent-gold: #c8a84b;
|
||
--accent-gold-bright: #e8c86b;
|
||
--accent-red: #8b3a3a;
|
||
--accent-green: #3a6b3a;
|
||
--scrollbar-thumb: #3a3020;
|
||
```
|
||
|
||
**Typography:**
|
||
- UI labels: `'Cinzel'` (Google Fonts) — classical, serif, fits the UO aesthetic
|
||
- Code / script content: `'JetBrains Mono'` or `'Fira Code'`
|
||
- Body / metadata: `'EB Garamond'`
|
||
|
||
### Gump Renderer Container
|
||
|
||
The 800x600 canvas sits inside a styled container:
|
||
```
|
||
┌─────────────────────────────────────┐
|
||
│ PaperDollGump │ ← class name header
|
||
│ Scripts/Gumps/PaperDollGump.cs │ ← file path
|
||
├─────────────────────────────────────┤
|
||
│ ┌───────────────────────────────┐ │
|
||
│ │ │ │
|
||
│ │ 800 × 600 canvas │ │
|
||
│ │ │ │
|
||
│ └───────────────────────────────┘ │
|
||
│ [Refresh] [Dynamic inputs →] │
|
||
└─────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## Claude Code Session Discipline
|
||
|
||
- **Always read this file first** before any code changes
|
||
- **One phase at a time** — do not begin Phase N+1 until Phase N deliverable is working
|
||
- **No placeholder implementations** — every function either works or is explicitly `todo!()` / `unimplemented!()`
|
||
- **Test asset parsing incrementally** — parse one file type, verify output in UI, then move to next
|
||
- **C# sidecar is a black box to Rust** — Rust only speaks JSON IPC, never references sidecar internals
|
||
- **SQLite is the source of truth** for indexed data — never re-parse files at runtime if SQLite has the data
|
||
- **Fake data state lives in React** — Zustand store, never in Rust or the sidecar
|
||
- **All Tauri commands return `Result<T, String>`** — errors surface to the frontend as readable messages
|
||
- **Asset pixel data is never stored in SQLite** — always read from .mul/.uop at request time, cached in Rust memory for the session
|
||
|
||
---
|
||
|
||
## Known Constraints & Decisions
|
||
|
||
| Decision | Rationale |
|
||
|---|---|
|
||
| Gump canvas fixed at 800×600 | Matches UO's original coordinate system; no scaling math needed |
|
||
| C# sidecar is a separate process | Roslyn requires .NET; keeping it isolated prevents Rust/C# FFI complexity |
|
||
| No live shard connection | Tool is purely local dev; no TCP/UDP to a running ServUO instance |
|
||
| Windows only (initial) | Tauri supports cross-platform but ServUO/.NET/UO client are Windows-primary |
|
||
| ServUO scripts not compiled | Roslyn used in analysis mode only; no attempt to actually build/run scripts |
|
||
| Open source MIT | Consistent with all Whitlocktech projects |
|
||
|
||
---
|
||
|
||
## Reference Resources
|
||
|
||
- **ClassicUO** (open source UO client, C#) — reference for all .mul/.uop parsers: https://github.com/ClassicUO/ClassicUO
|
||
- **UOFiddler** (open source UO asset editor, C#) — reference for tiledata, art, gump rendering: https://github.com/polserver/UOFiddler
|
||
- **ServUO** — target script codebase: https://github.com/ServUO/ServUO
|
||
- **Roslyn docs** — https://docs.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/
|
||
- **Tauri docs** — https://tauri.app/
|
||
- **mul format reference** — http://docs.polserver.com/packets/index.php (UO file format documentation)
|