From 24c8efe03d1625d55eb6647fcee6a77e2a812c1d Mon Sep 17 00:00:00 2001 From: Basil Date: Fri, 1 May 2026 10:03:27 +0800 Subject: [PATCH] refactor: implement binary parsing in L0 DataTable rows - Add BinaryReaderExtension with Vector2/3/4, Quaternion, Color, Rect types - Implement ParseDataRow(byte[]) in TagRow and RarityTagBudgetRow - Update CLAUDE.md with GameFramework module table and improved architecture docs - Remove unnecessary InternalsVisibleTo and L1 wrapper classes Co-Authored-By: Claude --- CLAUDE.md | 78 +++++++++++++-- .../DataTable/BinaryReaderExtension.cs | 97 +++++++++++++++++++ .../DataTable/RarityTagBudgetRow.cs | 17 +++- src/GeometryTD.Domain/DataTable/TagRow.cs | 17 +++- 4 files changed, 195 insertions(+), 14 deletions(-) create mode 100644 src/GeometryTD.Domain/DataTable/BinaryReaderExtension.cs diff --git a/CLAUDE.md b/CLAUDE.md index cc23663..37496d1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,10 +10,25 @@ This is a Unity project that **cannot build standalone** outside of Unity Editor ### Three-Layer Structure (per `docs/LayeredArchitectureDesign.md`) -- **L0 (Domain)**: Pure C# business logic with no Unity dependencies. Contains enums, constants, data structures, CombatNode domain, PlayerInventory, InventoryGeneration, UI use cases. -- **L1 (Infrastructure)**: Glue layer bridging L0 and Unity. Implements L0 interfaces, holds L0 service instances, manages Unity lifecycle. +- **L0 (Domain)**: Pure C# business logic. References `GameFramework.dll` (pure C#, no Unity dependency). Contains enums, constants, data structures, CombatNode domain, PlayerInventory, InventoryGeneration, UI use cases. +- **L1 (Infrastructure)**: Glue layer bridging L0 and Unity. Implements GameFramework Unity adapters (Resource/Scene/Entity/UI/Sound), implements L0 interfaces, holds L0 service instances, manages Unity lifecycle. - **L2 (Presentation)**: Unity MonoBehaviour classes, UGuiForm implementations, Entity Logic (Player, Enemy, Tower). +### GameFramework.dll Modules Usable in L0 + +GameFramework.dll is pure C# and provides infrastructure usable directly in L0: + +| Module | L0 Usage | +|--------|----------| +| Event | `GameFramework.Event.EventManager` - custom args inherit `GameEventArgs` | +| ObjectPool | `ObjectPoolManager` - entities inherit `ObjectBase` | +| Fsm | `FsmManager` - states inherit `FsmState` | +| ReferencePool | `ReferencePool.Acquire() / Release()` | +| DataNode | Tree data structure, use directly | +| DataTable | DataRow classes implement `IDataRow` in L0 | + +Modules requiring L1 Unity adapters: Resource, Scene, Entity, UI, Sound. + ### Key Domain Boundaries | Domain | Key Files | Responsibility | @@ -23,6 +38,7 @@ This is a Unity project that **cannot build standalone** outside of Unity Editor | **PlayerInventory** | `PlayerInventoryComponent.cs`, `PlayerInventoryTowerAssemblyService.cs` | Backpack, trading, tower assembly | | **InventoryGeneration** | `InventoryGenerationComponent.cs`, `DropPoolRoller.cs`, `ShopGoodsBuilder.cs` | Drops, shop goods, rewards | | **MapEntity** | `MapEntity.cs`, `MapTopologyService.cs`, `TowerPlacementService.cs` | Map orchestration, grid/path, tower placement | +| **TagSystem** | `TagGenerationRuleRegistry.cs`, `RarityTagBudgetRuleRegistry.cs`, `TagDefinitionRegistry.cs` | Tag generation rules and definitions | ### Combat State Machine Flow @@ -30,14 +46,25 @@ This is a Unity project that **cannot build standalone** outside of Unity Editor ### Dual-Currency System -- **Coin**: Single-combat internal currency (`CombatRunResourceStore.CurrentCoin`) -- **Gold**: Cross-combat persistent currency (`PlayerInventoryComponent.Gold`) +- **Coin**: Single-combat internal currency (`CombatRunResourceStore.CurrentCoin`). Used for build/upgrade/destroy in combat. Reset per combat from `DRLevel.StartCoin`. +- **Gold**: Cross-combat persistent currency (`PlayerInventoryComponent.Gold`). Used for shop buy/sell. Persists across nodes. ### Tag System -Three-table configuration: `Tag.txt`, `RarityTagBudget.txt`, `TagConfig.txt`. Current shipped 7 tags: Fire, Ice, Crit, Execution, Shatter, Inferno, AbsoluteZero. +Three-table configuration: `Tag.txt`, `RarityTagBudget.txt`, `TagConfig.txt`. -## Service Naming Conventions +**Active Tags (7)**: Fire, Ice, Crit, Execution, Shatter, Inferno, AbsoluteZero. + +**Deferred Tags (5)**: BurnSpread, IgniteBurst, FreezeMask, Pierce, Overpenetrate (metadata only, not combat-active). + +**Tag Runtime Structure**: +- Component instances hold `TagType[]` +- Tower instances hold `TagRuntimeData[]` (grouped by TagType with Stack count) +- `TagGenerationRuleRegistry` and `RarityTagBudgetRuleRegistry` load from `TagRow` / `RarityTagBudgetRow` (IDataRow implementations) + +**Tag Generation Uses `InventoryGenerationRandomContext`** for reproducibility (RunSeed + SourceType + ItemInstanceId + ConfigId). + +### Service Naming Conventions - `Scheduler`: State machine boundary only - `Manager`: Facade/aggregate entry for subdomain @@ -47,6 +74,8 @@ Three-table configuration: `Tag.txt`, `RarityTagBudget.txt`, `TagConfig.txt`. Cu - `Bridge`: Framework boundary adapter - `Runtime`: Mutable state carrier - `Resolver`: Mapping/lookup/resolution +- `Tracker`: Tracks running entities or factual truth values +- `Port`: Restricted host interface for internal state/use cases ## Key Invariants @@ -56,12 +85,41 @@ Three-table configuration: `Tag.txt`, `RarityTagBudget.txt`, `TagConfig.txt`. Cu 4. `EnemyLifecycleTracker` is sole source for `AliveEnemyCount` and `HasAliveBoss` 5. `MapEntity` accesses combat context via `MapData` + Events only 6. Tag generation uses `InventoryGenerationRandomContext` for reproducibility +7. `TowerPlacementService` is sole write entry for tower mapping state +8. `MapTopologyService` is sole source for Path/Foundation data + +## Project Structure + +``` +src/ +├── GeometryTD.Domain/ # L0 - Pure C#, references GameFramework.dll +│ ├── Definition/ +│ │ ├── Enum/ # All enums (TagType, RarityType, etc.) +│ │ ├── Constant/ # Constants +│ │ ├── DataStruct/ # POCOs (AttackPayload, HitContext, TowerStatsData) +│ │ └── Tag/ # Tag definitions, generation rules, combat effects +│ ├── Event/ # GameEventArgs subclasses by domain +│ ├── UI/ # UI contexts and use case interfaces +│ ├── DataTable/ # IDataRow implementations (TagRow, RarityTagBudgetRow) +│ └── CustomComponent/ # L0 services (CombatScheduler, EnemyManager, etc.) +│ +├── GeometryTD.Infrastructure/ # L1 - Unity adapter layer +│ ├── Entity/ # EntityLogic, EntityData DTOs +│ ├── Scene/Map/ # MapTopologyService, TowerPlacementService +│ ├── UI/ # UGuiForm base, controllers +│ ├── DataTable/ # L1 DataRowBase wrappers over L0 rows +│ └── CustomComponent/ # Unity Components holding L0 services +│ +└── GeometryTD.Presentation/ # L2 - Unity MonoBehaviours, Views + ├── Entity/Logic/ # Player, Enemy, Tower, Bullet entities + └── Components/ # MovementComponent, ShooterBullet, etc. +``` ## Documentation -- `docs/LayeredArchitectureDesign.md` - Three-layer architecture -- `docs/CombatNodeArchitecture.md` - Combat scheduler and state machine -- `docs/MapEntityArchitecture.md` - Map orchestration services -- `docs/TagSystemDesign.md` - Tag system rules +- `docs/LayeredArchitectureDesign.md` - Three-layer architecture, GameFramework integration, migration phases +- `docs/CombatNodeArchitecture.md` - Combat scheduler, state machine, EnemyManager, resource store +- `docs/MapEntityArchitecture.md` - Map orchestration, tower placement, topology services +- `docs/TagSystemDesign.md` - Tag system rules, generation, three-table config, combat effects - `docs/GameDesign.md` - High-level game design - `docs/MVP-Scope.md` - Current MVP scope diff --git a/src/GeometryTD.Domain/DataTable/BinaryReaderExtension.cs b/src/GeometryTD.Domain/DataTable/BinaryReaderExtension.cs new file mode 100644 index 0000000..da5d769 --- /dev/null +++ b/src/GeometryTD.Domain/DataTable/BinaryReaderExtension.cs @@ -0,0 +1,97 @@ +using System; +using System.IO; +using System.Numerics; + +namespace GeometryTD.Domain.DataTable +{ + public static class BinaryReaderExtension + { + public static Vector2 ReadVector2(this BinaryReader binaryReader) + { + return new Vector2(binaryReader.ReadSingle(), binaryReader.ReadSingle()); + } + + public static Vector3 ReadVector3(this BinaryReader binaryReader) + { + return new Vector3(binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle()); + } + + public static Vector4 ReadVector4(this BinaryReader binaryReader) + { + return new Vector4(binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle()); + } + + public static Quaternion ReadQuaternion(this BinaryReader binaryReader) + { + return new Quaternion(binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle()); + } + + public static Color ReadColor(this BinaryReader binaryReader) + { + return new Color(binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle()); + } + + public static Color32 ReadColor32(this BinaryReader binaryReader) + { + return new Color32(binaryReader.ReadByte(), binaryReader.ReadByte(), binaryReader.ReadByte(), binaryReader.ReadByte()); + } + + public static Rect ReadRect(this BinaryReader binaryReader) + { + return new Rect(binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle()); + } + + public static DateTime ReadDateTime(this BinaryReader binaryReader) + { + return new DateTime(binaryReader.ReadInt64()); + } + + public readonly struct Color + { + public float R { get; } + public float G { get; } + public float B { get; } + public float A { get; } + + public Color(float r, float g, float b, float a) + { + R = r; + G = g; + B = b; + A = a; + } + } + + public readonly struct Color32 + { + public byte R { get; } + public byte G { get; } + public byte B { get; } + public byte A { get; } + + public Color32(byte r, byte g, byte b, byte a) + { + R = r; + G = g; + B = b; + A = a; + } + } + + public readonly struct Rect + { + public float X { get; } + public float Y { get; } + public float Width { get; } + public float Height { get; } + + public Rect(float x, float y, float width, float height) + { + X = x; + Y = y; + Width = width; + Height = height; + } + } + } +} diff --git a/src/GeometryTD.Domain/DataTable/RarityTagBudgetRow.cs b/src/GeometryTD.Domain/DataTable/RarityTagBudgetRow.cs index 5a3f6b9..84b60f9 100644 --- a/src/GeometryTD.Domain/DataTable/RarityTagBudgetRow.cs +++ b/src/GeometryTD.Domain/DataTable/RarityTagBudgetRow.cs @@ -1,4 +1,6 @@ using System; +using System.IO; +using System.Text; using GameFramework.DataTable; using GeometryTD.Definition; @@ -38,7 +40,18 @@ namespace GeometryTD.Domain.DataTable public bool ParseDataRow(byte[] dataRowBytes, int startIndex, int length, object userData) { - throw new NotSupportedException("Binary parsing is not supported."); + using (MemoryStream memoryStream = new MemoryStream(dataRowBytes, startIndex, length, false)) + { + using (BinaryReader binaryReader = new BinaryReader(memoryStream, Encoding.UTF8)) + { + Id = binaryReader.Read7BitEncodedInt32(); + Rarity = (RarityType)binaryReader.Read7BitEncodedInt32(); + MinCount = binaryReader.Read7BitEncodedInt32(); + MaxCount = binaryReader.Read7BitEncodedInt32(); + } + } + + return true; } } -} \ No newline at end of file +} diff --git a/src/GeometryTD.Domain/DataTable/TagRow.cs b/src/GeometryTD.Domain/DataTable/TagRow.cs index 2a323d2..c8da333 100644 --- a/src/GeometryTD.Domain/DataTable/TagRow.cs +++ b/src/GeometryTD.Domain/DataTable/TagRow.cs @@ -1,4 +1,6 @@ using System; +using System.IO; +using System.Text; using GameFramework.DataTable; using GeometryTD.Definition; @@ -53,7 +55,18 @@ namespace GeometryTD.Domain.DataTable public bool ParseDataRow(byte[] dataRowBytes, int startIndex, int length, object userData) { - throw new NotSupportedException("Binary parsing is not supported."); + using (MemoryStream memoryStream = new MemoryStream(dataRowBytes, startIndex, length, false)) + { + using (BinaryReader binaryReader = new BinaryReader(memoryStream, Encoding.UTF8)) + { + Id = binaryReader.Read7BitEncodedInt32(); + MinRarity = (RarityType)binaryReader.Read7BitEncodedInt32(); + Weight = binaryReader.Read7BitEncodedInt32(); + IsImplemented = binaryReader.ReadBoolean(); + } + } + + return true; } } -} \ No newline at end of file +}