From 3e99c7eb302647e2c61007844c2baf310a0f9f1b Mon Sep 17 00:00:00 2001 From: REDCODE Date: Sun, 14 Jun 2026 15:27:01 +0200 Subject: [PATCH] Add internal architecture analysis (docs/INTERNALS.md) --- AGENTS.md | 38 +-- docs/INTERNALS.md | 672 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 692 insertions(+), 18 deletions(-) create mode 100644 docs/INTERNALS.md diff --git a/AGENTS.md b/AGENTS.md index 35e115a..6e74c9a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,43 +4,45 @@ Decompilation + modding re-architecture of "Slave Matrix" (Auto Eden). Three .NET 8 projects: - **SlaveMatrix** (`WinExe`) — game entrypoint `SlaveMatrix.Program.Main` at `SlaveMatrix/SlaveMatrix/GameClasses/Program.cs` -- **2DGAMELIB** (class library) — legacy rendering engine (GDI+) + core data types (BodyTemplate, ShapePart, etc.) +- **2DGAMELIB** (class library) — legacy GDI+ rendering engine + core data types (BodyTemplate, ShapePart, etc.) - **SlaveMatrix.Extract** (`Exe`) — asset pipeline CLI; depends on SlaveMatrix for embedded resources ## Build & Run ``` dotnet build Solution.sln # build all -./run.sh # launch game (cds to game_folder/, sets DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1) +./run.sh # cd game_folder/ + DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 + dotnet run dotnet run --project SlaveMatrix.Extract -- --output # asset extraction ``` -The game **must** run with `game_folder/` as working directory. `launchSettings.json` sets this automatically in VS/Rider. +Game **must** run with `game_folder/` as working directory. `launchSettings.json` sets `../game_folder` automatically. ## Key Quirks -- **C# 9.0** (`LangVersion` hardcoded in `.csproj`). No `ImplicitUsings` in SlaveMatrix/2DGAMELIB. -- **x64 only**, `AllowUnsafeBlocks=true`, `InvariantGlobalization=true` (needed on Linux). -- **BinaryFormatter** still in use (legacy saves). Requires `EnableUnsafeBinaryFormatterSerialization=true` and `NoWarn=SYSLIB0011`. -- **`System.Drawing.EnableUnixSupport`** must be set at startup (`runtimeconfig.template.json` + `AppContext.SetSwitch` in `Program.cs`). -- **Newtonsoft.Json** (not `System.Text.Json`) — used in 2DGAMELIB and Extract. -- **Empty `RootNamespace`** in SlaveMatrix and 2DGAMELIB — types live in global namespace. -- **`GenerateAssemblyInfo=false`** — uses legacy `Properties/AssemblyInfo.cs`. +- **C# 9.0** (`LangVersion` in `.csproj`). No `ImplicitUsings` in SlaveMatrix/2DGAMELIB. Extract has `ImplicitUsings=enable` + `Nullable=enable` — it can use modern C#. +- **x64 only**, `AllowUnsafeBlocks=true`, `InvariantGlobalization=true` (required on Linux). +- **BinaryFormatter** for legacy saves: `EnableUnsafeBinaryFormatterSerialization=true`, `NoWarn=SYSLIB0011`. +- **`System.Drawing.EnableUnixSupport`** set at startup (`runtimeconfig.template.json` + `AppContext.SetSwitch` in `Program.cs`). `NoWarn=CA1416` suppresses platform-compat warnings across all projects. +- **Newtonsoft.Json** (not `System.Text.Json`) in 2DGAMELIB and Extract. +- **Empty `RootNamespace`** in SlaveMatrix/2DGAMELIB — types live in global namespace. +- **`GenerateAssemblyInfo=false`** — legacy `Properties/AssemblyInfo.cs`. +- **`global.json`** pins SDK 8.0 with `rollForward=latestMajor, allowPrerelease=true`. - **No test projects**, no CI/CD, no formatter/linter config. -- **843 BodyPartClasses** (`SlaveMatrix/SlaveMatrix/BodyPartClasses/`). **Do not rename** — C# reflection (`Type.GetType`) resolves joint types by class name at runtime. +- **843 BodyPartClasses** (`SlaveMatrix/SlaveMatrix/BodyPartClasses/`). **Do not rename** — C# reflection (`Type.GetType`) resolves joint type names at runtime. -## Architecture Notes +## Architecture -- **BodyTemplate** (13 binary `Obj` resources) → `VariantGrid` → `MorphVariant` → `PartGroup` → `ShapePart` tree with cardinal-spline curves and joint points. Deserialized via `BinaryFormatter` + `RemappedTypeBinder` (type remapping for decompiled names). -- **Extract pipeline** (`SlaveMatrix.Extract`) loads the 13 embedded resources, applies `MigrateKeys()` (23-entry runtime KeyMap for Japanese→English), then exports: +- **BodyTemplate** (13 binary `Obj` resources) → `VariantGrid` → `MorphVariant` → `PartGroup` → `ShapePart` tree (cardinal-spline curves + joint points). Deserialized via `BinaryFormatter` + `RemappedTypeBinder`. +- **Extract pipeline** loads the 13 embedded resources, applies `MigrateKeys()` (20-entry runtime KeyMap for Japanese→English lookups), then exports: - Intermediate JSON → `extracted/` (gitignored) - SVG + YAML per part → `SlaveMatrix/Assets/Parts/` (checked in) - - `Catalog.yaml` index → `SlaveMatrix/Assets/` -- **EnglishNameMap** in `SlaveMatrix.Extract/Program.cs` (179 entries) is for extraction output naming only. It is separate from the smaller runtime `MigrateKeys` KeyMap which only covers keys the old game code actually looks up. -- **Phase 0 (extraction) is complete.** Phases 1-5 (engine rewrite) are planned in `PLAN.md`. + - `Catalog.yaml` → `SlaveMatrix/Assets/` +- **EnglishNameMap** in `SlaveMatrix.Extract/Program.cs` (~176 entries) is for extraction output naming only, separate from the runtime KeyMap. +- **game_folder/ content** is copied to build output via ``. +- **Phase 0 (extraction) is complete.** Phases 1-5 (engine rewrite, Vulkan/Silk.NET) are planned in `PLAN.md`. ## Git - `.gitignore` excludes: `**/bin/`, `**/obj/`, `.vs/`, `.idea/`, `Config.ini`, `game_folder/save/*`, `extracted/`. -- `SlaveMatrix/Assets/` (auto-generated SVGs + YAML) **is** checked in. +- `SlaveMatrix/Assets/` (auto-generated) **is** checked in. - `game_folder/` runtime assets are checked in (bgm, text, etc.). diff --git a/docs/INTERNALS.md b/docs/INTERNALS.md new file mode 100644 index 0000000..f825af5 --- /dev/null +++ b/docs/INTERNALS.md @@ -0,0 +1,672 @@ +# Slave Matrix Internal Architecture + +A detailed technical analysis of how the program works from startup to rendering a character on screen, with focus on asset loading and generalization strategy. + +--- + +## 1. Startup Pipeline + +``` +Program.Main() + │ + ├─ [STATIC INIT] Type initializer for GlobalState (before Main) + │ └─ Program static cctor (#1) + │ ├─ AppContext.SetSwitch("System.Drawing.EnableUnixSupport", true) + │ └─ RemappedTypeBinder.RegisterMapping for 3 game types + │ "SlaveMatrix.Ele" → Element + │ "SlaveMatrix.EleD" → ElementData + │ "SlaveMatrix.EleI" → ElementInstance + │ + ├─ GlobalState static cctor (#2) + │ ├─ Sets CurrentDirectory = AppContext.BaseDirectory + │ ├─ Loads 13 BodyTemplate Obj resources from embedded bytes + │ │ Each: byte[] → BinaryFormatter→byte[] → BinaryFormatter→BodyTemplate + │ ├─ MigrateKeys() — 20-entry KeyMap renames Japanese→English keys + │ ├─ Caches 843 BodyPartClass Type objects for reflection-based Element creation + │ ├─ Verifies type resolution (Type.GetType) for all race-part combos + │ └─ Sets imiPath, panPath for onomatopoeia/moaning text directories + │ + ├─ Main() + │ ├─ GlobalState.LoadConfig() — reads game_folder/Config.ini + │ │ Booleans: BigWindow, HighQuality, ShowFPS, etc. + │ │ + │ ├─ Create ModeEventDispatcher + │ │ ├─ UITitle = game title string + │ │ ├─ Unit = scaling factor (2203 HQ / 1101.5 LQ) + │ │ ├─ Base = Rectangle(4f, 3f, percent/100f) — aspect ratio + viewport + │ │ ├─ DisQuality = 1.0 + │ │ └─ HitAccuracy = 0.3 + │ │ + │ ├─ med.InitializeModes("Start", ModuleRegistry.GetMods) + │ │ └─ GetMods callback registers 25+ screen modules via RegisterModule() + │ │ + │ └─ med.Drawing() — creates GLFW window, enters main loop, never returns + │ + └─ [MAIN LOOP] med.Drawing() + ├─ GlImage.BitmapSetting(Display) + │ ├─ GLFW 3.3 + OpenGL 3.3 Core profile window, size = Display bitmap dims + │ ├─ Compiles vertex+ fragment shader (simple textured quad) + │ ├─ Creates full-screen triangle-strip VAO/VBO + │ └─ Sets up callbacks: Click, Move, Leave, Scroll, Resize → Module routing + │ + ├─ med.Setting(GlImage) + │ ├─ Creates Display Bitmap (Base.LocalWidth * Unit × Base.LocalHeight * Unit px) + │ ├─ Creates Hit Bitmap (same × HitAccuracy = 0.3) + │ ├─ Creates SceneFader for crossfade transitions + │ └─ Calls GetMods(this) → ModuleRegistry populates all Module delegates + │ + └─ while(Drive) + ├─ FPSF.FPSFixed(action) — fixed 60 fps timestep + │ └─ action: Modes[mode].Draw(FPSF) for active screen module + ├─ baseControl.PollEvents() — GLFW event pump + └─ baseControl.SetBitmap(Display) + ├─ glTexSubImage2D(GDI+ Bitmap → OpenGL texture) + ├─ glDrawArrays(TriangleStrip, 0, 4) + └─ Glfw.SwapBuffers(window) +``` + +### Key files + +| Step | File | Lines | +|------|------|-------| +| Program.Main | `SlaveMatrix/GameClasses/Program.cs` | 18-43 | +| GlobalState static init | `SlaveMatrix/GameClasses/GlobalState.cs` | 1087-1429 | +| ModeEventDispatcher ctor | `2DGAMELIB/ModeEventDispatcher.cs` | 78-99 | +| Drawing() loop | `2DGAMELIB/ModeEventDispatcher.cs` | 290-325 | +| Setting() | `2DGAMELIB/ModeEventDispatcher.cs` | 101-232 | +| GlImage.BitmapSetting | `2DGAMELIB/WPFImage.cs` | 146-238 | +| GlImage.SetBitmap | `2DGAMELIB/WPFImage.cs` | 99-144 | +| ModuleRegistry.GetMods | `SlaveMatrix/GameClasses/ModuleRegistry.cs` | 204-493 | + +--- + +## 2. Module System (Screen Management) + +Each "screen" is a `Module` struct — a set of delegate callbacks: + +```csharp +public struct Module { + public Action Draw; + public Action Down, Up, Move; + public Action Leave; + public Action Wheel; + public Action Setting; + public Action Dispose; +} +``` + +Registered via `ModeEventDispatcher.RegisterModule(string name, Module mod)`. 25 modules include: + +| Module Name | Purpose | +|-------------|---------| +| `Start` | Initial screen | +| `Title` | Title screen | +| `Credit` | Credits | +| `メインフォーム` (MainForm) | Primary HUD/gameplay | +| `対象` (Target) | Character target selection | +| `Training` | Training/interaction scene | +| `Blessing`, `Office` | Other gameplay scenes | +| `Debt` | Debt management | +| `SlaveShop`, `ToolShop` | Shop screens | +| `PlayerInformation` | Player status | +| `OP0`, `OP1`, `説明`, `初事務所` | Event scenes | +| `RepaymentEvent1-3` | Story events | + +**Mode switching**: `ModeEventDispatcher.Mode = name` calls `Leave` on the old module and `Setting` on the new module. + +**Module draw delegates** are defined inline in `ModuleRegistry.GetMods()` — typically compositing multiple `RenderArea` layers, characters, and UI elements. + +--- + +## 3. Asset Loading Architecture + +### 3.1 The 13 Binary Resources + +Embedded as `byte[]` in `SlaveMatrix/Properties/Resources.resx`, accessed via `Resources.xxx`: + +| # | Resource Name | Contents (after MigrateKeys) | +|---|---------------|------------------------------| +| 1 | `Resources.胴体` | Torso, Waist, Neck, Head, Chest, BackHair0/1, SideHair, etc. | +| 2 | `Resources.肩左` | Shoulder variants | +| 3 | `Resources.腕左` | Arm, UpperArm, LowerArm, + wing/beast/quadruped variants | +| 4 | `Resources.脚左` | Leg variants | +| 5 | `Resources.尻尾` | Tail variants | +| 6 | `Resources.半身` | Half-body composites | +| 7 | `Resources.肢左` | Limb variants | +| 8 | `Resources.肢中` | Middle limb variants | +| 9 | `Resources.性器` | Genital variants | +| 10 | `Resources.性器付` | Genitals with attachments | +| 11 | `Resources.スタンプ` | Stamp/stencil overlays | +| 12 | `Resources.カーソル` | Cursor graphics | +| 13 | `Resources.その他` | Miscellaneous | +| — | `Resources.タイル` | Tile/background patterns | + +### 3.2 Deserialization Pipeline + +``` +byte[] raw = Resources.胴体 + │ + ├─ Serializer.Load(raw) // FIRST deserialization + │ └─ new BinaryFormatter { Binder = RemappedTypeBinder } + │ .Deserialize(new MemoryStream(raw)) + │ └─ RemappedTypeBinder translates: + │ _2DGAMELIB.Obj → BodyTemplate (outer layer is Obj wrapping a byte[]) + │ └─ Result: byte[] innerData + │ + └─ innerData.ToDeserialObject() // SECOND deserialization + └─ new BinaryFormatter { Binder = RemappedTypeBinder } + .Deserialize(new MemoryStream(innerData)) + └─ RemappedTypeBinder translates: + _2DGAMELIB.Obj → BodyTemplate + _2DGAMELIB.Difs → VariantGrid + _2DGAMELIB.Dif → MorphVariant + _2DGAMELIB.Pars → PartGroup + _2DGAMELIB.Par → ShapePart + _2DGAMELIB.ParT → ShapePartT + _2DGAMELIB.Out → CurveOutline + _2DGAMELIB.Joi → JointPoint + └─ Result: BodyTemplate with full object graph +``` + +The **double-wrapping** is a legacy artifact: the `.resx` stores the data as `byte[]`, but the original source wrapped a `byte[]` containing a serialized `BodyTemplate` inside another `BinaryFormatter` envelope. + +### 3.3 Post-Deserialization + +1. **`SetDefaultR()`** — initializes all PartGroup runtime fields to defaults +2. **`MigrateKeys()`** — replaces 20 Japanese keys in `BodyTemplate.Difss` with English names +3. **Type caching** — iterates all `VariantGrid` keys + race suffixes (人/猫/獣/鳥/蜘蛛/蜥蜴/魚/蛙/四足/水棲/半人鮫/竜/植物/巨人/妖精/兎/狐/熊/馬/鹿/狼/河馬/恐/牛/羊/鬼/亜人/機械/蛇/狗/黄金/冥府/冥界/天使/墮天使) and caches `Type.GetType("BodyPartName_RaceSuffix")` for each. + +### 3.4 KeyMap (Runtime) + +Defined in `2DGAMELIB/ObjExtensions.cs` — 20 entries: + +| Japanese | English | +|----------|---------| +| 咳 → Cough | 腰 → Waist | 胴 → Torso | 首 → Neck | +| 頭 → Head | 後髪0 → BackHair0 | 後髪1 → BackHair1 | 横髪 → SideHair | +| 脚 → Leg | 腕 → Arm | 肩 → Shoulder | 胸 → Chest | +| 下腕 → LowerArm | 上腕 → UpperArm | +| 鳥翼上腕/獣翼上腕/四足上腕 → 鳥翼UpperArm/獣翼UpperArm/四足UpperArm | +| 鳥翼下腕/獣翼下腕/四足下腕 → 鳥翼LowerArm/獣翼LowerArm/四足LowerArm | + +### 3.5 game_folder Runtime Assets + +Copied to build output via `.csproj`: +```xml + +``` + +Loaded at runtime: +- **Text**: `GameText` static ctor loads `text/System/Race.txt`, `Attribute.txt`, `Common.txt`, and scene-specific text +- **Onomatopoeia/Moaning**: `GlobalState.Set擬音()` → `Imitation.txt`; `Set喘ぎ()` → `Pant/*.txt` +- **Config**: `game_folder/Config.ini` +- **BGM**: Expected in `bgm/` (currently commented out in code) +- **Saves**: `save/*.sav` (BinaryFormatter) or `.json` +- **Background images**: *loaded from embedded resources*, not game_folder (e.g., `Resources.dangeon01_ex2`) + +--- + +## 4. Data Model + +### 4.1 Hierarchy + +``` +BodyTemplate [Serializable] + └── OrderedDictionary "Difss" + ├── Key: "Waist", "Torso", "Neck", "Head", ... + │ + └── VariantGrid [Serializable] (.Difs) + ├── CountX : int ← morph grid width (ValueX axis) + ├── CountY : int ← morph grid height (ValueY axis) + ├── ValueX : double ← current X selection (0..1 → index) + ├── ValueY : double ← current Y selection (0..1 → index) + ├── PositionSize : Rectangle ← inherited from parent template + ├── PositionVector : Vector2D + ├── AngleBase : double + ├── SizeBase : double + └── List difs ← linear array [x * CountY + y] + │ + └── MorphVariant [Serializable] (.Dif) + ├── Tag : string + └── List parss + │ + └── PartGroup [Serializable] (.Pars) + ├── Tag : string + ├── Parent : PartGroup (runtime, non-serialized) + └── OrderedDictionary pars + ├── Key: "childPartName" + │ + ├── ShapePart [Serializable] (.Par) + │ ├── Dra : bool ← visibility + │ ├── Closed : bool + │ ├── PenWidth : double + │ ├── BasePoint : Vector2D ← local origin + │ ├── Position : Vector2D ← translation + │ ├── Angle : double ← rotation + │ ├── Size : double ← uniform scale + │ ├── SizeX / SizeY : double ← non-uniform scale + │ ├── HitColor : Color ← unique per part + │ ├── OP : List + │ │ ├── ps : List ← control points + │ │ ├── Tension : float ← cardinal spline + │ │ └── Outline : bool ← stroke-only? + │ ├── JP : List + │ │ └── Joint : Vector2D ← attachment point + │ ├── Brush : Brush (runtime, [JsonIgnore]) + │ └── Pen : Pen (runtime, [JsonIgnore]) + │ + ├── ShapePartT [Serializable] (.ParT) + │ └── extends ShapePart + │ ├── Text : string + │ ├── FontSize : double + │ └── Font : Font (runtime) + │ + └── PartGroup (recursive nesting) +``` + +### 4.2 Coordinate System + +- **Local space**: points in `CurveOutline.ps[]` — normalized 0..1 range +- **Part space**: transformed by `BasePoint`, `Position`, `Angle`, `Size/SizeX/SizeY` +- **Parent space**: transformed by parent chain (PartGroup hierarchy) +- **VariantGrid space**: further transformed by `VariantGrid.PositionVector`, `AngleBase`, `SizeBase` +- **Screen space**: multiplied by `Unit` (2203 or 1101.5) + +### 4.3 Joint System + +`SetJoints()` on `BodyTemplate` builds: +- **`pj`** (`Dictionary>`) — joints per PartGroup +- **`pr`** (`Dictionary`) — the root ShapePart for each PartGroup's joints +- **`JoinRoot`** — identifies the anchor point in parent space + +`JoinPA()` propagates angle changes through the chain: +``` +Parent.Angle → drives → Child.Position offset via joint point +``` + +### 4.4 Morph Variant Selection + +`VariantGrid.ValueX` (0..1) → `indexX = Clamp((int)(ValueX * (CountX - 1)), 0, CountX - 1)` +`VariantGrid.ValueY` (0..1) → `indexY = Clamp((int)(ValueY * (CountY - 1)), 0, CountY - 1)` +Current MorphVariant: `difs[indexX * CountY + indexY]` +Current PartGroup: `Current.parss[IndexY]` — then draws all children + +This means X axis iterates across `difs[]` while Y iterates within a `MorphVariant.parss[]`. The first entry (Y=0) of the current X gives the active PartGroup tree. + +--- + +## 5. Rendering Pipeline (Per-Frame) + +### 5.1 Compositing Layers + +``` +[Module.Draw(FPSF)] + │ + ├─ Med.HitGraphics.Clear(Color.Transparent) + │ + ├─ a.Draw(BasementBackground) ← background layer (embedded bitmap) + │ └─ RenderArea.Draw(RenderArea other) + │ └─ DisplayGraphics.DrawImage(other.DisplayLayer, ...) + │ + ├─ TrainingTarget.Draw(a, FPS) ← character layer + │ └─ Character.Draw(Area, FpsCounter) + │ + ├─ bs.Draw(a) ← UI button layer + ├─ dbs.Draw(a) ← bottom button layer + ├─ ip.Draw(a, FPS) ← info panel layer + ├─ SaveData.Draw(a) ← save data overlay + │ + └─ Med.Draw(a) ← composite to main display + └─ RenderArea.DrawTo(Med.Display, Med.Hit) + └─ DisplayGraphics.DrawImage(a.DisplayLayer, 0, 0) + └─ HitGraphics.DrawImage(a.HitLayer, 0, 0) +``` + +### 5.2 Character Draw + +``` +Character.Draw(Area, FPS) + ├─ Motions.Drive(FPS) — update all animation state + │ ├─ Breathing: oscillates Chest/Abdomen size + │ ├─ Blinking: triggers Eye morph (Xv/Yv → closed) + │ ├─ Tear/Cum/Squirt/Urination: fluid system updates + │ ├─ Sway: body angle oscillation + │ ├─ Eye tracking: head angle toward cursor + │ ├─ Cough/Moan: variant cycling + │ └─ User-defined morph motions + │ + └─ Body.描画(Area) + ├─ 接続PA() — propagate parent angles through joint chains + │ └─ For each Element: Element.Body.JoinPA() + │ └─ VariantGrid.JoinPA() recurses PartGroups + │ └─ ShapePart.Rotate() updates child positions + │ + ├─ Color updates: skin/dress/wing membrane/etc. + ├─ ElementInstance updates + │ + └─ Draw(Area) delegate + └─ Elements sorted by 描画前後 (back-to-front) + ├─ Element.描画0(Are) — main body + │ └─ VariantGrid.Draw(Are) + │ └─ Are.Draw(Current PartGroup) + │ └─ PartGroup.Draw(Unit, Graphics) + │ └─ for each child in pars: + │ ├─ if ShapePart + Dra: + │ │ ShapePart.Draw(Unit, Graphics) + │ ├─ if ShapePartT: + │ │ ShapePartT.DrawString(Unit, Graphics) + │ └─ if PartGroup: + │ recursive PartGroup.Draw() + │ + ├─ Element.描画1(Are) — overlay layer 1 + └─ Element.描画2(Are) — overlay layer 2 +``` + +### 5.3 ShapePart.Draw() + +``` +ShapePart.Draw(double Unit, Graphics g) + ├─ if (Edit flag) → Calculation(Unit): + │ ├─ BaseTransform = -BasePoint * Size (shifts to origin) + │ ├─ Position += ParentChain offset + │ ├─ Angle += ParentChain angle + │ ├─ Size *= ParentChain scale + │ ├─ For each CurveOutline: + │ │ └─ Transform points through: base → scale → rotate → translate + │ ├─ Build GraphicsPath.Path via AddCurve(pointArray, tension) + │ ├─ Build GraphicsPath.OutlinePath for stroke-only curves + │ └─ Clear Edit flag + │ + ├─ Brush.Color = resolve runtime color (from Element.ColorSet) + ├─ g.FillPath(Brush, Path) + └─ if (Outline curves exist) → g.DrawPath(Pen, OutlinePath) +``` + +### 5.4 Hit Detection + +Rendered on a separate lower-resolution `Hit` Bitmap. Each `ShapePart` has a unique `HitColor`. Module mouse callbacks check the pixel color under the cursor: + +```csharp +Color hit = Med.Hit.GetPixel((int)(pos.X * HitAccuracy), (int)(pos.Y * HitAccuracy)); +// match hit to Element via HitColor → find which part was clicked +``` + +### 5.5 RenderArea Hierarchy + +``` +RenderArea : Rectangle + ├── DisplayLayer : Bitmap (full-res, visible output) + ├── HitLayer : Bitmap (low-res, hit detection) + ├── DisplayGraphics : Graphics (wraps DisplayLayer) + ├── HitGraphics : Graphics (wraps HitLayer) + ├── Draw(ShapePart) → renders to both layers + ├── Draw(PartGroup) → recursive render + ├── Draw(RenderArea) → composite another area onto this one + └── DrawTo(display, hit) → final composite to Med.Display/Med.Hit + +ManagedArea : RenderArea + └── Lower-resolution render target for character body (3x3 internal cells) +``` + +--- + +## 6. Character System + +### 6.1 Element Class + +Each `Element` wraps a single `VariantGrid` from a BodyTemplate resource. 843 Element subclasses in `BodyPartClasses/` define concrete body parts with race-specific behavior. + +``` +Element [Serializable] + ├── 本体 : VariantGrid ← the template data + ├── 位置B / 位置C : Vector2D ← position base/contract + ├── 角度B / 角度C : double ← angle base/contract + ├── 尺度B / 尺度C : double ← scale base/contract + ├── Xv / Yv : double ← morph variant selection (drives ValueX/Y) + ├── 描画前後 : int ← render order (back:0, middle:1, front:2) + ├── 接続Type : int ← joint connection type + ├── ColorSlot : int ← which color set to use + ├── 拡張 : OrderedDictionary ← extension data + │ + ├── 描画0(Are) → 本体.Draw(Are) ← main render + ├── 描画1(Are) → 本体.Draw(Are) ← overlay 1 + └── 描画2(Are) → 本体.Draw(Are) ← overlay 2 +``` + +### 6.2 Element Construction via Reflection + +In `Body` constructor, Elements are instantiated by `Type.GetType()`: + +```csharp +// Simplified: +string typeName = $"_{partKey}_{raceSuffix}"; // e.g., "Head_人" +Type t = GlobalState.BodyTypeCache[typeName]; +Element ele = (Element)Activator.CreateInstance(t); +ele.本体 = GlobalState.BodyTemplates[resourceName].Difss[partKey]; +``` + +843 files mean 843 `Type` lookups. The reflection names use **original Japanese** keys (pre-MigrateKeys) for the `_人/猫/etc` suffix convention. + +### 6.3 Body Class + +``` +Body + ├── Elements : Element[] ← flat array of all parts + ├── Strongly-typed fields: + │ Waist, Torso, Chest, Neck, Head, + │ BackHair0, BackHair1, SideHair, BaseHair, FrontHair, + │ EyeL, EyeR, Nose, Mouth, EyebrowL, EyebrowR, + │ BreastL, BreastR, Belly, + │ Genitals, Anus, + │ UpperArmL/R, LowerArmL/R, HandL/R, + │ ShoulderL/R, + │ UpperLegL/R, LowerLegL/R, FootL/R, + │ WingL/R, Tail, + │ etc. (~80 fields) + │ + ├── 接続PA() — joint angle propagation + ├── 描画(Area) — main render entry + └── Draw(Area) delegate — sorted Element rendering +``` + +### 6.4 Character Class + +``` +Character + ├── Body : Body + ├── Motions : Motions ← animation controller + ├── FluidSystem : CharacterFluidSystem + ├── CharacterData : CharacterData ← race, colors, stats + │ + ├── Draw(Area, FPS) → Body.描画(Area) + └── Motions.Drive(FPS) — updates all animation state +``` + +### 6.5 Animation System (Motions) + +The `Motions` class manages a collection of `Motion` objects. Each `Motion` modifies Element properties over time: + +- **Breathing**: oscillates `Chest.尺度C` and `Belly.尺度C` using a sine wave +- **Blinking**: transitions `EyeL.Yv` / `EyeR.Yv` to 1 (closed) then back +- **Cough**: cycles `Cough.Xv` through morph variants +- **Sway**: oscillates body `角度B` (Angle Base) +- **Eye tracking**: sets `Head.角度C` toward cursor position +- **Fluid drip**: tears, drool, cum, squirt, urination — updates fluid system +- **Climax**: full-body spasm (randomized element position/angle jitter) + +Each `Motion` implements `Drive(FPS)` which time-samples a curve/function and applies deltas to `Element.Xv`, `Yv`, `位置B/C`, `角度B/C`, `尺度B/C`. + +--- + +## 7. Generalization Strategy + +### 7.1 Replace BinaryFormatter (Data Layer) + +**Current**: `byte[] → BinaryFormatter→byte[] → BinaryFormatter→BodyTemplate` + +**Target**: Load from structured files. Extraction is already done via `SlaveMatrix.Extract`: + +``` +Embedded .resx bytes ──[Extract CLI]──▶ extracted/*.json (full template data) + Assets/Parts/{id}/part.yaml (per-part metadata) + Assets/Parts/{id}/x{y}x{y}.svg (variant curves) + Assets/Catalog.yaml (runtime index) +``` + +**Strategy**: +1. Parse `Catalog.yaml` → discover all parts +2. For each part, load `part.yaml` → get morph dimensions, joints, fields, variant list +3. For active morph (Xv/Yv), load corresponding SVG → parse path data → extract control points +4. Rebuild `ShapePart`-equivalent geometry from SVG path data + +### 7.2 Replace GDI+ (Rendering Layer) + +**Current**: `System.Drawing.Graphics.FillPath/DrawPath` on GDI+ `GraphicsPath` built via `AddCurve(points, tension)`. + +**Target**: Vulkan/Silk.NET (or any modern GPU API). + +**Strategy**: +1. Cardinal spline → cubic Bezier conversion (already done in SVG export — `BuildSvgPath` in Extract) +2. Tessellate cubic Bezier curves into triangles (or use GPU path rendering) +3. Maintain the same transform chain: + ``` + localPoint → baseTransform → scale → rotate → translate → parentTransform → Unit + ``` +4. Replace hit-testing: render with unique color IDs to offscreen buffer, sample pixel + +### 7.3 Critical Data to Preserve + +``` +ShapePart (or equivalent): + - CurveOutline[] → Points[] (local coords), Tension, Outline flag + - Closed → fill vs stroke + - BasePoint → local origin + - Position → translation in parent space + - Angle → rotation + - Size, SizeX, SizeY → scale + - Joints → attachment positions in local coords + - Dra → visibility + - HitColor → unique ID for hit testing + +PartGroup (or equivalent): + - Tree hierarchy (ordered children) + - Parent reference + +VariantGrid (or equivalent): + - CountX, CountY → morph grid dimensions + - ValueX, ValueY → current selection → (xIndex, yIndex) + - PositionVector, AngleBase, SizeBase → root transform + +MorphVariant (or equivalent): + - Per-variant PartGroup tree +``` + +### 7.4 Joint System Generalization + +Current: `SetJoints()` builds proximity-based joint dictionaries. PartGroups have `JointPoint` lists that define connection anchors. + +Target: Explicit joint definitions in YAML (already in extracted `part.yaml`): + +```yaml +joints: + - position: [0.50, 0.47] # pre-computed world-space joint positions + - position: [0.48, 0.52] +``` + +**Strategy**: +1. Define explicit parent-child joint connections in a race template YAML +2. Match joint positions between adjacent parts (or name them explicitly) +3. `JoinPA()` equivalent: when parent rotates, propagate angle through the joint point offset + +### 7.5 Morph Variant Generalization + +Current: Discrete X/Y index into `List`. Selection via `(int)(ValueX * (CountX-1))`. + +Target: Support both discrete selection and interpolation: + +```yaml +# interpolate between variant x0y0 and x0y1 +morph: + x: 0.3 # selects x=0, interpolates 30% toward next X + y: 0.0 +``` + +**Strategy**: +1. Load both adjacent SVG variants +2. Interpolate control point positions: `p = lerp(p_a, p_b, t)` +3. Rebuild path geometry from interpolated points + +### 7.6 Suggested Implementation Order + +``` +Phase 1: Data layer + ├── JSON/YAML deserializer for extracted assets + ├── BodyTemplate/VariantGrid/MorphVariant ⇒ plain C# records + └── replaces BinaryFormatter dependency + +Phase 2: Rendering backend + ├── Curve tessellator (cardinal spline → bezier → triangles) + ├── Transform chain (preserve hierarchy logic) + ├── Hit detection (offscreen ID buffer) + └── ShapePart.Draw equivalent in new backend + +Phase 3: Character assembly + ├── Joint system (explicit YAML connections) + ├── Element system (part instance with morph state) + ├── Animation (Motion/Motions port) + └── Character.Draw equivalent + +Phase 4: Migration + ├── Port BodyPartClasses → data-driven definitions + ├── Port UI system → new rendering backend + ├── Replace Module system → scene graph + └── Wire game logic (training, shops, events) +``` + +--- + +## Appendix: Key File Index + +| File | Role | +|------|------| +| `SlaveMatrix/GameClasses/Program.cs` | Entry point, static cctor, Main | +| `SlaveMatrix/GameClasses/GlobalState.cs` | Global static state, template loading, type cache | +| `SlaveMatrix/GameClasses/ModuleRegistry.cs` | Module definitions, UI, game initialization | +| `SlaveMatrix/GameClasses/Character.cs` | Character class + animation | +| `SlaveMatrix/GameClasses/Body.cs` | Body assembly + rendering | +| `SlaveMatrix/GameClasses/Element.cs` | Element base class (VariantGrid wrapper) | +| `SlaveMatrix/GameClasses/ElementInstance.cs` | Runtime Element instance | +| `SlaveMatrix/GameClasses/ElementData.cs` | Serializable Element data | +| `SlaveMatrix/GameClasses/GameText.cs` | Game text loader | +| `SlaveMatrix/BodyPartClasses/*.cs` | 843 concrete Element subclasses | +| `2DGAMELIB/ModeEventDispatcher.cs` | Main loop, mode switching, GLFW integration | +| `2DGAMELIB/WPFImage.cs` | GLFW window, OpenGL texture upload (GlImage) | +| `2DGAMELIB/RenderArea.cs` | Offscreen render buffer (display + hit) | +| `2DGAMELIB/ManagedArea.cs` | Lower-res render area | +| `2DGAMELIB/SceneFader.cs` | Crossfade transition | +| `2DGAMELIB/Module.cs` | Module delegate struct | +| `2DGAMELIB/BodyTemplate.cs` | Top-level template container | +| `2DGAMELIB/VariantGrid.cs` | Morph variant grid | +| `2DGAMELIB/MorphVariant.cs` | Morph variant (PartGroup list) | +| `2DGAMELIB/PartGroup.cs` | Part group tree node | +| `2DGAMELIB/ShapePart.cs` | Drawable shape (curves, joints, transforms) | +| `2DGAMELIB/ShapePartT.cs` | Text-rendering shape | +| `2DGAMELIB/CurveOutline.cs` | Cardinal spline curve data | +| `2DGAMELIB/JointPoint.cs` | Joint anchor point | +| `2DGAMELIB/Joints.cs` | Joint connection management | +| `2DGAMELIB/OrderedDictionary.cs` | Serializable ordered dictionary | +| `2DGAMELIB/Vector2D.cs` | 2D vector struct | +| `2DGAMELIB/Serializer.cs` | BinaryFormatter + JSON serialization | +| `2DGAMELIB/RemappedTypeBinder.cs` | Legacy type name remapping for BinaryFormatter | +| `2DGAMELIB/ObjExtensions.cs` | KeyMap + MigrateKeys | +| `2DGAMELIB/GeometryUtils.cs` | ObjLoad/ObjLoadRaw extension methods | +| `2DGAMELIB/Motion.cs` | Single animation motion | +| `2DGAMELIB/Motions.cs` | Motion collection controller | +| `2DGAMELIB/FpsCounter.cs` | Fixed-timestep FPS lock | +| `SlaveMatrix.Extract/Program.cs` | Asset extraction CLI (BinaryFormatter→JSON/SVG/YAML) | +| `SlaveMatrix/Properties/Resources.resx` | Embedded resource manifest | +| `SlaveMatrix/Properties/Resources.Designer.cs` | Strongly-typed resource accessors | +| `SlaveMatrix/Resources/*` | Raw binary resource files | +| `game_folder/` | Runtime assets (text, bgm, save, config) | +| `Assets/Catalog.yaml` | Generated global catalog | +| `Assets/Parts/{id}/` | Generated per-part SVG+YAML |