docs: refresh strategic roadmap + Foundation phase design spec
Output of a brainstorming session after Phase 6/7.1/9.1/9.2 shipped and the lifestone crystal bug was isolated. Two documents: 1. docs/plans/2026-04-11-roadmap.md — strategic roadmap replacing the stale post-Phase-5 version. Reflects what's actually shipped, reorganizes upcoming work into Phases A (Foundation), B (Gameplay), C (Polish — includes VFX/particles, dynamic lights, palette tuning, double-sided translucents), D (UI + Sound), and E (long-tail). Updates the "when will my complaint be fixed" quick-lookup with the correct phase for portals (VFX, not shader tricks as previously claimed), smoke, fireplace fire, and everything we fixed this session. Phase ordering: A → B → (C/D in parallel) → E. 2. docs/superpowers/specs/2026-04-11-foundation-phase-design.md — detailed implementation spec for Phase A only. Covers the four sub-pieces (streaming landblock loader, frustum culling, net I/O thread, async dat decoding folded into the streaming worker), their components, data flow, error handling, testing strategy, and commit-point ordering. Includes non-goals to prevent scope creep. No code changes yet. The spec goes to user review next, then into the writing-plans skill for a detailed implementation plan. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6f1971aa9c
commit
2c1c784c8c
2 changed files with 468 additions and 144 deletions
|
|
@ -1,174 +1,145 @@
|
|||
# acdream — phase roadmap
|
||||
# acdream — strategic roadmap
|
||||
|
||||
**Status:** Living document. Updated 2026-04-11 after Phase 5 visual verification.
|
||||
**Purpose:** Every observed defect and missing feature has a named phase that owns it. When something looks wrong, look here first to find which phase will fix it.
|
||||
**Status:** Living document. Updated 2026-04-11 after Phase 6, 7.1, 9.1, 9.2 landed.
|
||||
**Purpose:** One source of truth for where the project is and where it's going. Every observed defect or missing feature has a named phase that owns it; when something looks wrong in-game, look here to find the phase that'll address it. Implementation details live in per-phase specs under `docs/superpowers/specs/`, not in this file.
|
||||
|
||||
---
|
||||
|
||||
## Phases done
|
||||
## Phases already shipped
|
||||
|
||||
| Phase | What landed | Verification |
|
||||
|---|---|---|
|
||||
| **1** | Terrain rendering, plugin host scaffold | Visual ✓ |
|
||||
| **2a** | Static stabs/buildings (126 entities) | Visual ✓ |
|
||||
| **2b** | Textured 3×3 landblock grid + FlyCamera + IGameState | Visual ✓ |
|
||||
| **2c** | Procedural scenery (419 trees/rocks/bushes) | Visual ✓ |
|
||||
| **2d** | Interior EnvCell walker (475 static interior objects) | Visual ✓ |
|
||||
| **3a/3b** | Directional sun lighting + per-vertex terrain normals | Visual ✓ |
|
||||
| **3c** | Per-cell terrain texture blending (alpha atlas) | Visual ✓ |
|
||||
| **4** | Full UDP codec + handshake + character login + WorldSession | Live ✓ |
|
||||
| **5** | ObjDesc: AnimPart + TextureChanges + SubPalettes + ObjScale + Placement.Resting | Live ✓ partial |
|
||||
| 1 | Terrain rendering, plugin host scaffold | Visual ✓ |
|
||||
| 2a | Static stabs/buildings (126 entities) | Visual ✓ |
|
||||
| 2b | Textured 3×3 landblock grid + FlyCamera + IGameState | Visual ✓ |
|
||||
| 2c | Procedural scenery (419 trees/rocks/bushes) | Visual ✓ |
|
||||
| 2d | Interior EnvCell walker (475 static interior objects) | Visual ✓ |
|
||||
| 3a/3b | Directional sun lighting + per-vertex terrain normals | Visual ✓ |
|
||||
| 3c | Per-cell terrain texture blending (alpha atlas) | Visual ✓ |
|
||||
| 4 | Full UDP codec + handshake + character login + WorldSession | Live ✓ |
|
||||
| 5 | ObjDesc: AnimPartChange + TextureChanges + SubPalettes + ObjScale + Placement.Resting | Live ✓ |
|
||||
| 6.1 | Idle motion frame resolution (MotionResolver MVP) | Live ✓ |
|
||||
| 6.2 | Server-sent `MovementData` stance + forward command honored | Live ✓ |
|
||||
| 6.3 | Server-supplied `MotionTableId` override (fixes drudge statue) | Live ✓ |
|
||||
| 6.4 | Per-frame animation playback (breathing, idle cycles) | Live ✓ |
|
||||
| 6.5 | Slerp between keyframes for smooth animation | Live ✓ |
|
||||
| 6.6 | `UpdateMotion` (0xF74C) parser + dispatch to animation tick | Live ✓ |
|
||||
| 6.7 | `UpdatePosition` (0xF748) parser + position reseating | Live ✓ |
|
||||
| 7.1 | EnvCell room geometry — walls/floors/ceilings via CellStruct + Environment dats | Visual ✓ |
|
||||
| 9.1 | Translucent render pass (AlphaBlend / Additive / InvAlpha + per-kind blend funcs) | Visual ✓ |
|
||||
| 9.2 | Back-face culling in translucent pass (fixes lifestone crystal) | Visual ✓ |
|
||||
|
||||
**Phase 5 status:** characters, doors, signs, NPCs, statues all spawn and render with correct positions, scales, and per-entity textures + palettes. The Holtburg sign now stands upright thanks to Resting placement. The Nullified Statue of a Drudge renders at correct scale and color but in the wrong pose (deferred — see Phase 6).
|
||||
Plus polish that doesn't get its own phase number:
|
||||
- FlyCamera default speed lowered + Shift-to-boost
|
||||
- SurfaceDecoder: PFID_P8 / PFID_R8G8B8 / PFID_X8R8G8B8 decoders
|
||||
- GfxObjMesh: emit both pos and neg sides of double-sided polygons
|
||||
- EnvCell mesh Z-lift to fix ground-floor / terrain flicker
|
||||
|
||||
---
|
||||
|
||||
## Phases ahead, in suggested order
|
||||
## Phases ahead — agreed order
|
||||
|
||||
### Phase 6 — Animation system
|
||||
**Owns:**
|
||||
- Drudge statue rendering in aggressive crouch instead of upright "Resting" stance (creatures don't have a Resting placement frame; their idle pose comes from animations, not Setup placements)
|
||||
- All characters and NPCs rendering in Setup-default pose (T-pose-ish or default crouch) instead of breathing/idling
|
||||
- No walk/attack/gesture animations for any entity
|
||||
- Player character has no movement animation when WASD-flying
|
||||
### Phase A — Foundation (next)
|
||||
|
||||
**What it requires:**
|
||||
- `MotionTable` dat parser (animation frame keyframes per motion id)
|
||||
- `Animation` dat parser (the actual frame data)
|
||||
- Per-entity animation state: current motion id, current frame, time-since-frame-start
|
||||
- Per-frame interpolation between keyframes
|
||||
- Apply interpolated frame to PartArray.Frames per part (replaces our static `Setup.PlacementFrames[Default]` lookup)
|
||||
- Default-on-spawn motion: `Motion_Ready` or similar idle motion for every alive creature
|
||||
**Goal:** walk across 10+ landblocks without crashes, without hitches at landblock boundaries, and without framerate cratering.
|
||||
|
||||
**Reference:**
|
||||
- `references/ACViewer/ACViewer/Physics/PartArray.cs::UpdateParts` — the per-frame motion application loop
|
||||
- `references/ACViewer/ACViewer/Physics/Animation/AnimSequence.cs` — sequence interpolation
|
||||
- `references/ACE/Source/ACE.DatLoader/FileTypes/MotionTable.cs` — motion table layout
|
||||
**Sub-pieces:**
|
||||
- **A.1 — Streaming landblock loader.** Runtime-configurable visible window (default 5×5, `ACDREAM_STREAM_RADIUS` env var override). Center follows the camera offline and the player in live mode. Background worker thread loads landblocks CPU-side (dats, scenery, interior, entities); the render thread drains a completion outbox and performs GPU upload. Unloads happen at `radius + 1` distance to avoid churn.
|
||||
- **A.2 — Frustum culling + LOD.** Per-landblock AABB test against the view frustum in `StaticMeshRenderer.Draw`, skipping drawn entities in culled landblocks. Per-entity culling deferred. No LOD mesh levels yet — that's Phase C or later.
|
||||
- **A.3 — Background net I/O thread.** `WorldSession` runs its receive loop on a dedicated thread; parsed game messages are posted to a concurrent queue the render thread drains from `OnUpdate`. Event invocations still happen on the render thread (preserves existing handler assumptions). Removes packet drops under frame stalls.
|
||||
- **A.4 — Async dat decoding.** Folded into the streaming worker — it's the worker's read path, not a separate subsystem. Called out here because regressions in dat caching could land on this surface.
|
||||
|
||||
**Estimated effort:** 1-2 sessions. The motion system is the single biggest visual quality lever left.
|
||||
**Acceptance:**
|
||||
- Walk across 10+ landblocks in any direction, no crashes, no empty voids.
|
||||
- Landblock-boundary crossings produce no visible hitch.
|
||||
- Runtime window radius toggleable via environment variable.
|
||||
|
||||
**Detailed spec:** `docs/superpowers/specs/2026-04-11-foundation-phase-design.md`
|
||||
|
||||
---
|
||||
|
||||
### Phase 7 — Multi-floor interiors + dungeons
|
||||
**Owns:**
|
||||
- "Interior houses missing second floors" — our Phase 2d EnvCell walker only walks the ground-floor cells of a landblock and stops
|
||||
- "Doors that lead under something" — opening a door reveals subterranean cells we don't load
|
||||
- Dungeons (the Holtburg foundry interior, mines, etc.) — entirely missing
|
||||
- Building roofs and upper-story walls
|
||||
### Phase B — Gameplay / interaction
|
||||
|
||||
**What it requires:**
|
||||
- Walk EnvCell `0xAAAA0100+N` for the FULL range, not just N=0 (currently we stop early for some setups)
|
||||
- Recognize "stairs" / "floor transitions" in the cell graph and walk them
|
||||
- Handle dungeon landblock format: `0xAAAA0000` family with interior-only cell hierarchies
|
||||
- Possibly: load the dungeon when the player approaches a door that leads to it (streaming)
|
||||
**Goal:** actually play the game — walk the character on the server, click NPCs, pick up items, chat, basic combat loop.
|
||||
|
||||
**Reference:**
|
||||
- ACViewer's level loader and ACE's `EnvCell` parsing
|
||||
- WorldBuilder's dat browser for inspecting EnvCell structures of multi-floor buildings
|
||||
**Sub-pieces:**
|
||||
- **B.1 — Outbound ack pump.** Background timer that sends sequence acks every ~250ms. Without this the server drops idle clients after ~30s regardless of any other activity.
|
||||
- **B.2 — `PlayerAutonomousMove` outbound.** Wire WASD + camera state (or a dedicated player-controlled movement mode) to an outbound movement message so the server's view of the character matches ours.
|
||||
- **B.3 — Collision against terrain.** Required for the server to accept moves at all — ACE rejects client positions that are inside geometry or in disallowed Z ranges. Minimum viable: sample the terrain heightmap beneath the player and clamp Z. Proper: walk the `CellBSP` / `PhysicsBSP` we already parse.
|
||||
- **B.4 — `Use` / `UseWithTarget` / `PickUp`.** Outbound interaction messages. Drives opening doors, looting, talking to vendors.
|
||||
- **B.5 — Chat.** `SendTell`, `SendChat` outbound + receive/display inbound (display side depends on Phase D.1).
|
||||
|
||||
**Estimated effort:** 1 session, mostly debugging the cell-walk graph.
|
||||
**References:**
|
||||
- `references/ACE/Source/ACE.Server/Network/Handlers/MovementHandler.cs`
|
||||
- `references/ACE/Source/ACE.Server/Network/Handlers/UseObjectHandler.cs`
|
||||
- `references/holtburger/src/session/send.rs` for outbound packet-building patterns
|
||||
|
||||
**Acceptance:** walk on-server with your character, open a door, talk to an NPC, send a chat message and see the echo.
|
||||
|
||||
---
|
||||
|
||||
### Phase 8 — Player input → server
|
||||
**Owns:**
|
||||
- "I can fly around in acdream but I'm not actually MOVING in the game world from the server's perspective"
|
||||
- Standing still while live → server eventually drops us (no ack pump → no heartbeat)
|
||||
- Can't pick up items, click NPCs, talk to vendors, cast spells
|
||||
- No combat
|
||||
### Phase C — Polish / visuals
|
||||
|
||||
**What it requires:**
|
||||
- Outbound `PlayerAutonomousMove` GameMessage from acdream → server
|
||||
- Outbound `Ack` pump (background timer that sends `AckSequence` packets every ~250ms)
|
||||
- Outbound interact: `Use`, `UseWithTarget`, `PickUp`
|
||||
- Outbound chat: `SendTell`, `SendChat`
|
||||
- Map acdream's WASD camera position to server-space player position
|
||||
- Resolve character collision against terrain (so the server accepts the move)
|
||||
**Goal:** close the visible gaps that make the world read as "old / broken" compared to retail.
|
||||
|
||||
**Reference:**
|
||||
- ACE.Server `Network/Handlers/MovementHandler.cs`, `UseObjectHandler.cs`
|
||||
- holtburger session/send.rs for the outbound side patterns
|
||||
**Sub-pieces:**
|
||||
- **C.1 — VFX / particle system.** `PhysicsScript` parser, per-entity `ParticleEmitter` state, billboarded-quad particle renderer that lives in the Phase 9.1/9.2 translucent pass. Delivers **portal swirls, chimney smoke, and fireplace flames** in one implementation.
|
||||
- **C.2 — Dynamic point lights.** Fireplaces and lamps need local lighting; small upgrade to the mesh shader to accumulate N (e.g., 4) nearest point lights per draw. Uniform-buffer or UBO-friendly layout.
|
||||
- **C.3 — Palette range tuning.** Small per-range offset/length tweaks to match retail skin/hair/eye colors. Mostly diff and verify work, no architecture change.
|
||||
- **C.4 — Double-sided translucent polys.** Edge case left by Phase 9.2: neg-side translucent polys are culled because cull is always BACK. Fix by tracking per-sub-mesh `CullMode` and flipping GL state per draw (or drawing twice with opposite cull). Minor.
|
||||
- **C.5 — Shadow mapping (optional).** Deferred unless it becomes a bottleneck in screenshots — dynamic shadows are a known complexity trap.
|
||||
|
||||
**Estimated effort:** 1-2 sessions. The ack pump is small; the move-and-interact protocol surface is large.
|
||||
**References:**
|
||||
- `references/ACE/Source/ACE.DatLoader/FileTypes/PhysicsScript.cs` for the emitter schema
|
||||
- `references/ACViewer/ACViewer/Physics/Particles/` for the visual model
|
||||
- `references/WorldBuilder/Chorizite.OpenGLSDLBackend/Lib/ParticleBatcher.cs` for the Silk.NET-flavored implementation
|
||||
|
||||
**Acceptance:** portals look like swirly gates, chimneys smoke, fireplaces burn, character skin matches retail screenshots.
|
||||
|
||||
---
|
||||
|
||||
### Phase 9 — Visual polish
|
||||
**Owns:**
|
||||
- Portals rendering as black squares (probably needs special material handling — clipmap + animated UV)
|
||||
- Some entities with mesh-origin offsets that look wrong (non-Resting setups still float or sink)
|
||||
- Skin/hair/eye palette ranges on characters that aren't quite right
|
||||
- Lighting shadows and reflections
|
||||
- Better color tone overall
|
||||
### Phase D — UI / HUD + Sound
|
||||
|
||||
**What it requires:**
|
||||
- Portal-specific shader path or texture animation system
|
||||
- Per-Setup mesh-origin diagnostic and fixup (or accept and document)
|
||||
- Verify palette range offsets/lengths against retail screenshots
|
||||
- Optionally: shadow maps, dynamic lighting
|
||||
**Goal:** chat window, nameplates, inventory, and audio. Can run concurrently with Phase B or C because it doesn't touch gameplay/net/rendering surfaces.
|
||||
|
||||
**Estimated effort:** 1 session. Mostly tuning and edge cases.
|
||||
**Sub-pieces:**
|
||||
- **D.1 — 2D ortho overlay + font rendering.** Separate shader and render pass drawn after 3D. Font: FreeType via Silk.NET bindings, or bitmap fonts as a simpler first pass.
|
||||
- **D.2 — Chat window + nameplates.** First UI widgets. Chat consumes Phase B.5 messages; nameplates render per-entity 3D-to-2D projected labels.
|
||||
- **D.3 — Inventory / character / spell panels.** Requires a widget framework (layout, focus, input routing). Scope unbounded — ship minimum viable first.
|
||||
- **D.4 — Sound.** `SoundTable` parser, `Sound` dat decode, audio engine (OpenAL via Silk.NET.OpenAL), per-entity 3D positional audio, optional music.
|
||||
|
||||
**Acceptance:** see other players' chat in a chat window, see nameplates above NPCs, hear footsteps and sword hits.
|
||||
|
||||
---
|
||||
|
||||
### Phase 10 — UI / HUD
|
||||
**Owns:**
|
||||
- No chat window
|
||||
- No inventory display
|
||||
- No character info panel
|
||||
- No spell book
|
||||
- No minimap
|
||||
- No nameplates above characters/NPCs
|
||||
### Phase E — Long-tail
|
||||
|
||||
**What it requires:**
|
||||
- 2D rendering layer (Silk.NET supports this via ortho projection + a separate shader)
|
||||
- Font rendering (port a TTF rasterizer, e.g. FreeType bindings via Silk.NET, or use bitmap fonts)
|
||||
- Layout system or fixed-position widgets
|
||||
- Bind to GameState events for inventory updates, chat messages, etc.
|
||||
Not detailed here; each gets its own brainstorm when it becomes relevant.
|
||||
|
||||
**Estimated effort:** 2-3 sessions. UI is unbounded.
|
||||
- **Dungeon landblocks** (`0xAAAA0000` family) + teleport-on-door-click + server-side portal handling
|
||||
- **Phase 7.2 multi-floor stair walking** — cells reachable via portals the cell-walker doesn't cross
|
||||
- **Player character full rig** (held weapons, spell effects, death/revive animation)
|
||||
- **Weather + day/night cycle**
|
||||
- **Spellcasting pipeline**
|
||||
- **Group/fellowship UI**
|
||||
|
||||
---
|
||||
|
||||
### Phase 11 — Sound
|
||||
**Owns:**
|
||||
- No audio at all
|
||||
## Cross-cutting work tracked in parallel
|
||||
|
||||
**What it requires:**
|
||||
- `SoundTable` parser (each weenie has a SoundTable for footsteps, hits, voice)
|
||||
- Sound dat decode (probably WAV-like format)
|
||||
- Audio engine (NAudio or OpenAL via Silk.NET.OpenAL)
|
||||
- Per-entity 3D positional audio
|
||||
- Music
|
||||
|
||||
**Estimated effort:** 1-2 sessions.
|
||||
- **Test coverage.** Each phase lands with unit + integration tests in `tests/`. Current count: 98 Core + 96 Core.Net = 194. Keep the ratio as new phases land.
|
||||
- **Memory files.** Project state under `memory/project_phase_*_state.md` is updated when a phase ships. `MEMORY.md` is the index.
|
||||
- **`CLAUDE.md` discipline.** Check all four references (ACE, ACViewer, WorldBuilder, Chorizite) before committing to an approach. WorldBuilder is the closest stack match and should be checked first.
|
||||
|
||||
---
|
||||
|
||||
### Phase 12 — Streaming + perf
|
||||
**Owns:**
|
||||
- Currently we render a fixed 3×3 landblock window. Walking out of it would crash or show emptiness.
|
||||
- No frustum culling. Some parts of the world are always submitted.
|
||||
- No LOD. Distant trees use the same vertex count as near ones.
|
||||
- Single-threaded packet pump. Could miss packets under load.
|
||||
## Explicitly out of scope
|
||||
|
||||
**What it requires:**
|
||||
- Chunk-based terrain loading (deferred Phase 3d work — port WorldBuilder's `TerrainRenderManager`)
|
||||
- Visibility scan as the player moves; load nearby chunks, unload far ones
|
||||
- Frustum cull chunks before submitting draw calls
|
||||
- Background thread for net I/O so render thread is never blocked
|
||||
- Async dat decoding to avoid hitches when new entities arrive
|
||||
|
||||
**Estimated effort:** 1-2 sessions for streaming, additional polish ongoing.
|
||||
|
||||
---
|
||||
|
||||
## Things explicitly out of scope
|
||||
|
||||
- **Server emulation** — we use ACE for server, never reimplement
|
||||
- **Account creation** — direct user to ACE's auto-create or manual DB
|
||||
- **Anti-cheat / GMS / live-ops** — irrelevant for personal use
|
||||
- **Cross-platform** — Windows-only is fine; Silk.NET is cross-platform but the dat assumptions assume retail Windows install paths
|
||||
- **Server emulation** — we use ACE for server, never reimplement.
|
||||
- **Account creation** — direct user to ACE tooling.
|
||||
- **Anti-cheat / GM tools / live-ops** — irrelevant for personal use.
|
||||
- **Cross-platform support** — Windows-only; the dat path assumptions depend on retail Windows install layout. Silk.NET is cross-platform but we don't promise.
|
||||
- **Custom game content** — this is a client for existing AC data, not a toolchain.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -176,22 +147,27 @@
|
|||
|
||||
| Observation | Phase |
|
||||
|---|---|
|
||||
| Drudge statue in wrong pose (crouch) | **Phase 6 (Animation)** |
|
||||
| Characters in T-pose / wrong idle | **Phase 6** |
|
||||
| Houses missing second floors | **Phase 7 (Interiors)** |
|
||||
| Doors leading underground / nowhere | **Phase 7** |
|
||||
| Foundry interior missing | **Phase 7** |
|
||||
| Portals are black squares | **Phase 9 (Polish)** |
|
||||
| Holtburg sign half-buried | **Phase 5d FIXED** ✓ |
|
||||
| Statue too small / wrong size | **Phase 5c FIXED** ✓ |
|
||||
| Characters naked | **Phase 5a FIXED** ✓ |
|
||||
| Wrong colors on characters / statue | **Phase 5b FIXED** ✓ |
|
||||
| Foundry statue not appearing at all | **Phase 4.7 FIXED** ✓ |
|
||||
| Skin/hair color slightly off | **Phase 9** (likely a palette range tweak) |
|
||||
| Walking around doesn't move me on the server | **Phase 8 (Player input)** |
|
||||
| Can't talk to NPCs | **Phase 8** |
|
||||
| No chat window | **Phase 10 (UI)** |
|
||||
| No sound | **Phase 11** |
|
||||
| Can't walk to the next landblock | **Phase 12 (Streaming)** |
|
||||
| Drudge statue in wrong pose | **6.3 FIXED** ✓ |
|
||||
| Characters in T-pose / wrong idle | **6.1 FIXED** ✓ |
|
||||
| No breathing on NPCs | **6.4 + sentinel fix FIXED** ✓ |
|
||||
| Lifestone crystal has one side missing | **9.2 FIXED** ✓ |
|
||||
| Ground floor flickering with terrain | **7.1 FIXED** ✓ |
|
||||
| Houses missing second floors / walls | **7.1 FIXED** ✓ (interior mesh landed) |
|
||||
| Character clothing missing / wrong | **5 FIXED** ✓ |
|
||||
| Statue wrong color / wrong scale | **5 FIXED** ✓ |
|
||||
| Holtburg sign half-buried | **5 FIXED** ✓ |
|
||||
| Can't walk past the loaded 3×3 window | **Phase A (Foundation)** |
|
||||
| Frame hitch crossing landblock boundary | **Phase A** |
|
||||
| Walking around doesn't move me on the server | **Phase B (Gameplay)** |
|
||||
| Can't talk to NPCs | **Phase B** |
|
||||
| Can't open a door | **Phase B** |
|
||||
| Portals render as a rotating black disk | **Phase C.1 (VFX)** |
|
||||
| Chimneys have no smoke | **Phase C.1** |
|
||||
| Houses have no fireplace fire | **Phase C.1** |
|
||||
| No fireplace / torch lighting | **Phase C.2** |
|
||||
| Skin/hair color slightly off | **Phase C.3** |
|
||||
| No chat window | **Phase D.2** |
|
||||
| No sound | **Phase D.4** |
|
||||
| Dungeons / foundry interior missing | **Phase E** |
|
||||
|
||||
If you see something not on this list, tell me and I'll add it.
|
||||
If you see something not on this list, add it here and assign a phase.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue