Pool Oracle - Prefab Library & Entity Ecosystem Design
Pool Oracle - Prefab Library & Entity Ecosystem Design
Section titled “Pool Oracle - Prefab Library & Entity Ecosystem Design”Overview
Section titled “Overview”This document defines the architecture for RentEarth’s prefab library and entity ecosystem, establishing a single source of truth for all game objects that can be instantiated at runtime. The system integrates with the existing Bakery recipe system and supports both direct prefab references and Addressables-based loading.
Note: Implementation files are located in
Assets/Scripts/Pool/Oracle/namespaceRentEarth.Pool.Oracle. The “Oracle” name was chosen because “Library” is a reserved Unity folder that gets gitignored.
Current Architecture
Section titled “Current Architecture”Existing Components
Section titled “Existing Components”flowchart TB
subgraph BAKERY["BAKERY SYSTEM"]
PPR[PoolPrefabRecipe<br/>Editor UI]
TCR[ThemeConfigRecipe]
SCR[SupabaseConfig<br/>Recipe]
PPR --> PPC
subgraph CONFIG["ScriptableObject"]
PPC[PoolPrefabConfig.asset<br/>- characterPrefabs<br/>- entityPrefabs<br/>- environmentPrefabs<br/>- resourcePrefabs]
end
end
subgraph RUNTIME["RUNTIME SYSTEM"]
PS[PoolService<br/>Pure C# Logic]
PB[PoolBehaviour<br/>MonoBehaviour<br/>Transform containers]
PB --> PS
PS --> OP[ObjectPool<T><br/>- Prewarm, Get, Return, Clear<br/>- Active entity tracking by networkId]
end
PPC --> PS
Current Entry Types (from Proto)
Section titled “Current Entry Types (from Proto)”| Category | Proto Enum | Current Values |
|---|---|---|
| Character | CharacterType | Knight, Barbarian, Mage, Rogue, RogueHooded |
| Entity | EntityType | Player, NPC, Enemy, Boss |
| Environment | EnvironmentObjectType | Tree, Rock, Bush, Grass |
| Resource | ResourceType | Wood, Stone, Berries, Herbs, None |
Proposed Extensions
Section titled “Proposed Extensions”1. Prefab Registry System
Section titled “1. Prefab Registry System”Create a centralized registry that extends beyond the current config to support:
- Prefab Metadata: Display name, description, tags, preview thumbnails
- Variant Chains: Define fallback variants automatically
- Route Paths: Standardized asset paths for discoverability
- Validation Rules: Custom validation per prefab type
New ScriptableObject: PrefabRegistry
Section titled “New ScriptableObject: PrefabRegistry”[CreateAssetMenu(fileName = "PrefabRegistry", menuName = "KBVE/Pool/Prefab Registry")]public class PrefabRegistry : ScriptableObject{ [Header("Registry Metadata")] public string registryId; // Unique identifier public string displayName; // Human-readable name public string description; // Purpose description
[Header("Prefab Collections")] public List<PrefabDefinition> prefabs;
[Header("Route Configuration")] public string basePath = "Assets/Prefabs"; // Default search path public List<RouteMapping> routeMappings; // Category -> path mappings}
[Serializable]public class PrefabDefinition{ public string prefabId; // Unique string ID (e.g., "char_knight_v0") public PoolCategory category; // Entity, Environment, Character, Resource public int typeId; // Proto enum value public int variantId; // 0 = default
[Header("Asset Reference")] public GameObject prefab; // Direct reference (Editor) public string addressableKey; // Addressables key (Runtime)
[Header("Metadata")] public string displayName; // "Knight (Default)" public string description; // Tooltip/docs public string[] tags; // Searchable tags public Sprite thumbnail; // Preview image
[Header("Variant Info")] public int fallbackVariantId = -1; // -1 = no fallback, 0+ = fallback to variant public bool isDefaultVariant; // True if this is the default for type
[Header("Pool Settings")] public int poolSize = 10; // Initial pool size public int maxPoolSize = 50; // Maximum pool size public bool prewarmOnLoad = true; // Include in prewarm}
[Serializable]public class RouteMapping{ public PoolCategory category; public string relativePath; // e.g., "Characters", "Environment/Trees" public string addressableLabel; // Addressables label for batch loading}2. Route System
Section titled “2. Route System”Define standardized paths for prefab organization:
Assets/Prefabs/├── Characters/│ ├── Player/│ │ ├── Knight/│ │ │ ├── Knight_v0.prefab (default)│ │ │ ├── Knight_v1.prefab (variant 1)│ │ │ └── Knight_v2.prefab (variant 2)│ │ ├── Barbarian/│ │ ├── Mage/│ │ └── Rogue/│ └── NPC/│ └── [NPC prefabs...]├── Entities/│ ├── Player/│ ├── Enemy/│ └── Boss/├── Environment/│ ├── Trees/│ │ ├── Tree_Oak_v0.prefab│ │ ├── Tree_Pine_v0.prefab│ │ └── ...│ ├── Rocks/│ ├── Bushes/│ └── Grass/└── Resources/ ├── Wood/ ├── Stone/ ├── Berries/ └── Herbs/3. Bakery Integration: PrefabLibraryRecipe
Section titled “3. Bakery Integration: PrefabLibraryRecipe”New recipe that extends PoolPrefabRecipe with:
public class PrefabLibraryRecipe : IRecipe{ public string Name => "Prefab Library"; public string Description => "Manage prefab registry, routes, and pool configurations";
// Features: // 1. Registry Browser - View/edit all registered prefabs // 2. Route Scanner - Auto-discover prefabs from route paths // 3. Validation Dashboard - Check for missing/broken references // 4. Bulk Operations - Batch update pool settings // 5. Export/Import - JSON/YAML serialization for CI/CD}Bakery UI Flow
Section titled “Bakery UI Flow”flowchart TB
subgraph PREFAB_LIBRARY["PREFAB LIBRARY"]
subgraph SIDEBAR["Routes Panel"]
R1[Characters]
R2[Entities]
R3[Environment]
R4[Resources]
ADD[+ Add Route]
end
subgraph MAIN["Route Configuration"]
RC[Category: Characters<br/>Path: Assets/Prefabs/Characters<br/>Addressable Label: Characters]
SCAN[Scan Route]
VAL[Validate]
end
subgraph DISCOVERED["Discovered Prefabs"]
P1["✓ Knight_v0.prefab - CharacterType.Knight"]
P2["✓ Knight_v1.prefab - Variant 1"]
P3["! Knight_v2.prefab - Unregistered"]
P4["✓ Barbarian_v0.prefab - CharacterType.Barb"]
end
subgraph VALIDATION["Validation Status"]
V1["✓ All CharacterType enums have default prefabs"]
V2["! Missing: EntityType.Boss (no prefab assigned)"]
V3["✓ Environment prefabs validated"]
V4["! 2 orphaned prefabs found (not in registry)"]
end
SIDEBAR --> MAIN
MAIN --> DISCOVERED
DISCOVERED --> VALIDATION
end
4. Tag & Label System
Section titled “4. Tag & Label System”A comprehensive tagging system for searchable, filterable prefab management:
Tag Categories
Section titled “Tag Categories”| Tag Type | Purpose | Examples |
|---|---|---|
| Functional | Gameplay behavior | interactable, destructible, harvestable, npc |
| Visual | Rendering/appearance | animated, static, particle-emitter, lod-enabled |
| Physics | Collision/physics | solid, trigger, kinematic, ragdoll |
| Performance | Optimization hints | gpu-instanced, batched, lightweight, heavy |
| Semantic | Content classification | friendly, hostile, neutral, quest-related |
Tag Registry ScriptableObject
Section titled “Tag Registry ScriptableObject”[CreateAssetMenu(fileName = "TagRegistry", menuName = "KBVE/Pool/Tag Registry")]public class PrefabTagRegistry : ScriptableObject{ [Header("Tag Definitions")] public List<TagDefinition> tagDefinitions;
[Header("Label Groups")] public List<LabelGroup> labelGroups; // For batch operations
// Fast lookup by tag name private Dictionary<string, TagDefinition> _tagLookup;
public bool HasTag(string tagName) => _tagLookup.ContainsKey(tagName); public TagDefinition GetTag(string tagName) => _tagLookup.GetValueOrDefault(tagName);}
[Serializable]public class TagDefinition{ public string tagName; // "interactable" public string displayName; // "Interactable Object" public string description; // "Can be interacted with by player" public TagCategory category; // Functional, Visual, Physics, etc. public Color editorColor; // For Bakery UI highlighting public bool isSystemTag; // Reserved tags (can't be deleted)
[Header("Validation")] public List<string> requiredComponents; // Components that must exist public List<string> incompatibleTags; // Can't coexist with these tags}
[Serializable]public class LabelGroup{ public string groupName; // "Combat Entities" public string[] includedTags; // Tags in this group public string addressableLabel; // For Addressables batch loading}
public enum TagCategory{ Functional, Visual, Physics, Performance, Semantic, Custom}Tag Query API
Section titled “Tag Query API”public class PrefabQuery{ private readonly PrefabRegistry _registry;
// Fluent query interface public PrefabQuery WithTag(string tag); public PrefabQuery WithAnyTag(params string[] tags); public PrefabQuery WithAllTags(params string[] tags); public PrefabQuery WithoutTag(string tag); public PrefabQuery InCategory(PoolCategory category); public PrefabQuery WithLabel(string label);
public List<PrefabDefinition> Execute(); public PrefabDefinition First(); public int Count();}
// Usage examples:var combatEntities = registry.Query() .WithAnyTag("hostile", "friendly") .WithTag("animated") .InCategory(PoolCategory.Entity) .Execute();
var heavyPrefabs = registry.Query() .WithTag("heavy") .WithoutTag("gpu-instanced") .Execute();5. Unity Layer Configuration
Section titled “5. Unity Layer Configuration”Integration with Unity’s layer system for physics, rendering, and raycasting:
Layer Assignment Schema
Section titled “Layer Assignment Schema”[Serializable]public class LayerConfiguration{ [Header("Physics Layers")] public int defaultLayer = 0; // Default Unity layer public int playerLayer = 8; // "Player" layer public int enemyLayer = 9; // "Enemy" layer public int environmentLayer = 10; // "Environment" layer public int interactableLayer = 11; // "Interactable" layer public int projectileLayer = 12; // "Projectile" layer public int triggerLayer = 13; // "Trigger" layer
[Header("Rendering Layers")] public uint defaultRenderingLayer = 1; // Default rendering layer mask public uint outlineRenderingLayer = 2; // For outline effects public uint shadowOnlyLayer = 4; // Shadow casters only
[Header("Layer Matrix Presets")] public LayerCollisionPreset[] collisionPresets;}
[Serializable]public class LayerCollisionPreset{ public string presetName; // "Standard Combat" public LayerMask playerCollidesWith; // What player collides with public LayerMask enemyCollidesWith; // What enemies collide with public LayerMask projectileCollidesWith; // What projectiles hit}Layer Configuration Flow
Section titled “Layer Configuration Flow”flowchart LR
subgraph PHYSICS["Physics Layers"]
L0["[0] Default"]
L8["[8] Player ●"]
L9["[9] Enemy ●"]
L10["[10] Environment ●"]
L11["[11] Interactable ●"]
L12["[12] Projectile ●"]
L13["[13] Trigger ●"]
end
subgraph RENDERING["Rendering Layers"]
R1["[1] Default"]
R2["[2] Outline"]
R4["[4] ShadowOnly"]
R8["[8] Custom1"]
end
subgraph PRESETS["Collision Presets"]
P1["Standard Combat<br/>Player ↔ Enemy<br/>Player ↔ Environment<br/>Projectile ↔ Enemy"]
P2["Trigger Zones<br/>Player ↔ Trigger"]
end
PHYSICS --> PRESETS
6. Variant Architecture (Component Preservation)
Section titled “6. Variant Architecture (Component Preservation)”Critical design principle: Variants modify visual/behavioral aspects WITHOUT overwriting physics configurations, mesh colliders, or component settings that are expensive to reconfigure.
Variant Inheritance Model
Section titled “Variant Inheritance Model”flowchart TB
subgraph BASE["Base Prefab (v0)"]
T[Transform]
MF[MeshFilter]
MR[MeshRenderer]
MC[MeshCollider - PRESERVED]
RB[Rigidbody - PRESERVED]
CC[Custom Components]
end
subgraph VARIANTS["Variants inherit and can override:"]
V1["v1: Different materials/textures"]
V2["v2: Different mesh (same collider bounds)"]
V3["v3: Different animator controller"]
end
BASE --> VARIANTS
style MC fill:#4CAF50
style RB fill:#4CAF50
Component Preservation Schema
Section titled “Component Preservation Schema”[Serializable]public class VariantDefinition{ public int variantId; public string variantName; // "Armored", "Winter", "Elite" public string description;
[Header("Inheritance")] public int baseVariantId = 0; // Which variant this extends public VariantInheritanceMode inheritanceMode;
[Header("Preserved Components (Never Overwritten)")] public ComponentPreservationRules preservation;
[Header("Overridable Properties")] public VariantOverrides overrides;}
public enum VariantInheritanceMode{ /// <summary>Full prefab replacement - use for completely different models</summary> FullReplace,
/// <summary>Runtime property swap - preserves physics/colliders</summary> PropertyOverride,
/// <summary>Material/texture only - fastest, no structural changes</summary> MaterialOnly,
/// <summary>Additive - adds components without removing base ones</summary> Additive}
[Serializable]public class ComponentPreservationRules{ [Header("Physics Preservation")] public bool preserveMeshCollider = true; public bool preserveBoxCollider = true; public bool preserveSphereCollider = true; public bool preserveCapsuleCollider = true; public bool preserveRigidbody = true; public bool preserveJoints = true;
[Header("Transform Preservation")] public bool preserveChildHierarchy = true; public bool preserveLocalPositions = true; public bool preserveLocalRotations = true; public bool preserveLocalScales = true;
[Header("Component Preservation")] public string[] preservedComponentTypes; // Full type names to never override public string[] preservedChildPaths; // Child GameObjects to never modify
[Header("Layer Preservation")] public bool preservePhysicsLayers = true; public bool preserveRenderingLayers = true;}Pool Reset with Component Preservation
Section titled “Pool Reset with Component Preservation”public interface IPooledEntity{ string NetworkId { get; set; } PoolKey PoolKey { get; }
/// <summary>Called when entity is retrieved from pool</summary> void OnSpawned(EntitySpawnData data);
/// <summary>Called when entity is returned to pool</summary> void OnDespawned();
/// <summary> /// Reset entity state WITHOUT destroying physics components. /// This is called before OnSpawned for re-used entities. /// </summary> void OnReset(ComponentPreservationRules rules);}
public class PooledEntityBase : MonoBehaviour, IPooledEntity{ // Cached component references - populated once on first spawn private MeshCollider _meshCollider; private Rigidbody _rigidbody; private bool _componentsInitialized;
public virtual void OnReset(ComponentPreservationRules rules) { // Cache components on first use if (!_componentsInitialized) { _meshCollider = GetComponent<MeshCollider>(); _rigidbody = GetComponent<Rigidbody>(); _componentsInitialized = true; }
// Reset transform transform.localPosition = Vector3.zero; transform.localRotation = Quaternion.identity; transform.localScale = Vector3.one;
// Reset rigidbody state (but preserve configuration!) if (_rigidbody != null && rules.preserveRigidbody) { _rigidbody.velocity = Vector3.zero; _rigidbody.angularVelocity = Vector3.zero; // DO NOT change: mass, drag, constraints, isKinematic, etc. }
// MeshCollider - just ensure it's enabled, don't rebuild if (_meshCollider != null && rules.preserveMeshCollider) { _meshCollider.enabled = true; // DO NOT call: _meshCollider.sharedMesh = ... (expensive!) }
// Reset any runtime state ResetRuntimeState(); }
protected virtual void ResetRuntimeState() { // Override in subclasses for custom state reset }}7. LOD Integration (WebGL Critical)
Section titled “7. LOD Integration (WebGL Critical)”Why LOD matters for WebGL: WebGL runs in browsers with strict memory limits (~2GB max), limited GPU memory, and no native mesh streaming. Proper LOD management is essential for maintaining stable frame rates and preventing out-of-memory crashes.
LOD Strategy
Section titled “LOD Strategy”flowchart LR
subgraph LOD_TIERS["LOD Distance Tiers"]
LOD0["LOD0 (Full Detail)<br/>0-15m<br/>100% triangles<br/>Full materials<br/>All bones/animations"]
LOD1["LOD1 (Medium)<br/>15-40m<br/>50% triangles<br/>Simplified mats<br/>Reduced bones"]
LOD2["LOD2 (Low)<br/>40-80m<br/>25% triangles<br/>Single mat<br/>No bones"]
LOD3["LOD3/Cull<br/>80m+<br/>Billboard or Cull"]
end
LOD0 --> LOD1 --> LOD2 --> LOD3
Category-Specific LOD Defaults
Section titled “Category-Specific LOD Defaults”| Category | LOD0 | LOD1 | LOD2 | Cull | Impostor | Shadow Mode |
|---|---|---|---|---|---|---|
| Character (Player) | 0-15m | 15-30m | 30-60m | 80m | No | LOD1AndBelow |
| Character (NPC) | 0-10m | 10-25m | 25-50m | 60m | Yes | LOD0Only |
| Entity (Enemy) | 0-12m | 12-30m | 30-60m | 70m | Yes | LOD1AndBelow |
| Environment (Tree) | 0-20m | 20-50m | 50-100m | 150m | Yes | LOD0Only |
| Environment (Rock) | 0-15m | 15-40m | 40-80m | 120m | No | LOD0Only |
| Resource | 0-8m | 8-20m | 20-35m | 50m | No | None |
LOD Configuration Schema
Section titled “LOD Configuration Schema”[Serializable]public class LODConfiguration{ [Header("LOD Distances")] public float[] lodDistances = { 15f, 40f, 80f }; // Transition distances public float cullDistance = 100f; // Full cull distance
[Header("Quality Settings")] public LODQualityPreset qualityPreset = LODQualityPreset.Balanced; public bool useCrossFade = true; // Smooth transitions public float crossFadeDuration = 0.5f; // Fade time in seconds
[Header("WebGL Optimizations")] public bool enableAggressiveCulling = true; // More aggressive on WebGL public bool useImpostors = true; // Billboard at far distances public bool reduceBoneCountAtDistance = true; // Simplify skinned meshes public int maxBonesLOD1 = 32; // Max bones for LOD1 public int maxBonesLOD2 = 16; // Max bones for LOD2
[Header("Memory Management")] public bool streamLODsOnDemand = false; // Load LODs as needed (not WebGL) public bool unloadHighLODWhenFar = true; // Free memory for distant objects}
public enum LODQualityPreset{ Performance, // Aggressive LOD, early culling (mobile/WebGL) Balanced, // Default settings Quality, // Higher detail distances Ultra // Maximum detail (desktop only)}WebGL-Specific LOD Optimizations
Section titled “WebGL-Specific LOD Optimizations”public static class WebGLLODOptimizer{ /// <summary> /// Apply WebGL-specific optimizations to all pooled entities. /// Called during pool initialization. /// </summary> public static void OptimizeForWebGL(PrefabRegistry registry) {#if UNITY_WEBGL // Reduce LOD distances by 30% foreach (var prefab in registry.prefabs) { if (prefab.lodSettings != null && prefab.lodSettings.useCustomDistances) { for (int i = 0; i < prefab.lodSettings.customLodDistances.Length; i++) { prefab.lodSettings.customLodDistances[i] *= 0.7f; } } }
// Force impostor mode for complex meshes foreach (var prefab in registry.prefabs) { if (prefab.HasTag("complex") || prefab.HasTag("heavy")) { prefab.lodSettings.generateImpostor = true; } }
// Disable shadows for environment objects at distance foreach (var prefab in registry.prefabs) { if (prefab.category == PoolCategory.Environment) { prefab.lodSettings.shadowMode = ShadowLODMode.LOD0Only; prefab.lodSettings.shadowCullDistance = 30f; } }#endif }
/// <summary> /// Memory-aware LOD adjustment based on current memory pressure. /// </summary> public static void AdjustLODForMemoryPressure(float memoryUsagePercent) { if (memoryUsagePercent > 0.8f) { // Critical: Force lowest LOD for distant objects QualitySettings.lodBias = 0.5f; QualitySettings.maximumLODLevel = 1; } else if (memoryUsagePercent > 0.6f) { // Warning: Reduce detail QualitySettings.lodBias = 0.7f; QualitySettings.maximumLODLevel = 0; } else { // Normal: Restore default QualitySettings.lodBias = 1.0f; QualitySettings.maximumLODLevel = 0; } }}8. Entity System Integration
Section titled “8. Entity System Integration”Connect the prefab library to the entity lifecycle:
public interface IPooledEntity{ string NetworkId { get; set; } PoolKey PoolKey { get; }
void OnSpawned(EntitySpawnData data); void OnDespawned(); void OnReset();}
public struct EntitySpawnData{ public string NetworkId; public Vector3 Position; public Quaternion Rotation; public int VariantId; public Dictionary<string, object> CustomData; // Flexible payload}Spawn Flow
Section titled “Spawn Flow”sequenceDiagram
participant Server
participant Client
participant SnapshotHandler
participant PoolService
participant PrefabRegistry
participant ObjectPool
participant IPooledEntity
Server->>Client: Snapshot (entity)
Client->>SnapshotHandler: Process
SnapshotHandler->>PoolService: GetEntity()
PoolService->>PrefabRegistry: GetPrefab()
PrefabRegistry-->>PoolService: PrefabDefinition
PoolService->>ObjectPool: Get()
ObjectPool-->>PoolService: GameObject
PoolService->>IPooledEntity: OnSpawned()
Implementation Plan
Section titled “Implementation Plan”Phase 1: Foundation (COMPLETED)
Section titled “Phase 1: Foundation (COMPLETED)”-
Create
PrefabDefinitionandRouteMappingclasses- Define the data structures
- Add serialization support
- Files:
PrefabDefinition.cs,RouteMapping.cs
-
Create
PrefabRegistryScriptableObject- Implement basic CRUD operations
- Add validation methods
- File:
PrefabRegistry.cs
Phase 2: Tag & Layer System (COMPLETED)
Section titled “Phase 2: Tag & Layer System (COMPLETED)”-
Create
PrefabTagRegistryScriptableObject- Define tag categories and definitions
- Implement tag validation rules
- Add label group management
- File:
PrefabTagRegistry.cs
-
Create
LayerConfigurationScriptableObject- Define physics layer mappings
- Define rendering layer masks
- Add collision matrix presets
- File:
LayerConfiguration.cs
-
Implement
PrefabQueryAPI- Fluent query interface for tag-based searches
- Integration with PrefabRegistry
- Performance-optimized lookups
- File:
PrefabQuery.cs
Phase 3: Variant Architecture (COMPLETED)
Section titled “Phase 3: Variant Architecture (COMPLETED)”-
Create
VariantDefinitionand preservation rules- Define ComponentPreservationRules
- Implement VariantInheritanceMode enum
- Add variant override structures
- File:
VariantDefinition.cs
-
Implement
VariantApplicator- Material-only variant application
- Property override application
- Additive component support
- Collider/physics preservation
- Included in:
VariantDefinition.cs
-
Create
PrefabLODSettingsfor WebGL optimization- LODConfiguration ScriptableObject
- Per-prefab LOD settings
- Impostor and animation LOD support
- File:
PrefabLODSettings.cs
Phase 4: Bakery Integration (COMPLETED)
Section titled “Phase 4: Bakery Integration (COMPLETED)”-
Create
PrefabOracleRecipe- Route browser UI with tabbed interface (Prefabs, Tags, Layers, LOD, Validation)
- Prefab scanner with category statistics and badges
- Registration workflow with JSON export
- File:
Editor/KBVE/Bakery/Recipes/PrefabOracleRecipe.cs
-
Create Tag Manager UI
- Tag browser with category filtering
- Add/remove tags with color indicators
- System tag protection
- Initialize defaults functionality
- Integrated into PrefabOracleRecipe “Tags” tab
-
Validation Dashboard
- Missing prefab checks
- Tag validation across prefabs
- Layer configuration validation
- Combined results with severity indicators
- Integrated into PrefabOracleRecipe “Validation” tab
Phase 5: Runtime Integration (COMPLETED)
Section titled “Phase 5: Runtime Integration (COMPLETED)”-
Update
PoolServiceto usePrefabRegistry- Lazy loading from registry (LazyCreateAndGet method)
- Fallback chain support (Registry.GetPrefab handles fallbacks)
- Tag-based prefab queries (QueryByTag, HasTag methods)
- Configuration priority: Oracle → Legacy → Addressables
- Backward compatible with PoolPrefabConfig
- File:
Pool/PoolService.cs
-
Implement variant switching
- Runtime variant application (SwitchVariant method)
- Hot-swap detection (CanHotSwap)
- Cold swap with position/rotation preservation
- Variant tracking in ActiveEntityInfo
- File:
Pool/PoolService.cs
-
Add Addressables support
- Async prefab loading (existing, preserved)
- Label-based batch loading (existing, preserved)
- Registry-aware prewarm (PrewarmFromRegistryAsync)
- Per-category async prewarm
Phase 6: Polish (COMPLETED)
Section titled “Phase 6: Polish (COMPLETED)”-
Export/Import system
- JSON serialization for registry (
RegistrySerializer.cs) - Tag registry export (
ExportTagsToJson) - CI/CD integration (JSON file format)
- Import with options (overwrite, create new, resolve paths)
- Clipboard support (copy/paste JSON)
- Integrated into Validation tab in PrefabOracleRecipe
- JSON serialization for registry (
-
Protobuf as Source of Truth
- Extended
pool.protowithPrefabDefinition,PrefabRegistry,LODSettings,TagDefinitionmessages - Proto-to-ScriptableObject sync (
ProtoSync.cs) - Binary protobuf export/import in
RegistrySerializer - Enables server-client data alignment for prefab metadata
- C# classes auto-generated via
./scripts/generate-unity-protos.sh
- Extended
Naming Conventions
Section titled “Naming Conventions”Prefab Files
Section titled “Prefab Files”{Category}_{Type}_{Subtype}_v{Variant}.prefab
Examples:- Char_Knight_Default_v0.prefab- Char_Knight_Armored_v1.prefab- Env_Tree_Oak_v0.prefab- Env_Tree_Oak_Winter_v1.prefab- Res_Wood_Log_v0.prefabPrefab IDs (in Registry)
Section titled “Prefab IDs (in Registry)”{category}_{type}_v{variant}
Examples:- char_knight_v0- char_knight_v1- env_tree_oak_v0- res_wood_v0Addressable Labels
Section titled “Addressable Labels”Pool/{Category}Pool/{Category}/{Type}
Examples:- Pool/Characters- Pool/Characters/Knight- Pool/Environment- Pool/Environment/TreesConfiguration Files
Section titled “Configuration Files”prefab-registry.json (Export Format)
Section titled “prefab-registry.json (Export Format)”{ "registryId": "main", "displayName": "Main Prefab Registry", "version": "1.0.0", "prefabs": [ { "prefabId": "char_knight_v0", "category": "Character", "typeId": 0, "variantId": 0, "assetPath": "Assets/Prefabs/Characters/Knight/Knight_v0.prefab", "addressableKey": "char_knight_v0", "displayName": "Knight (Default)", "tags": ["player", "melee", "starter", "animated", "solid"], "labels": ["Pool/Characters", "Combat"], "poolSize": 10, "maxPoolSize": 50, "prewarmOnLoad": true, "isDefaultVariant": true, "layerSettings": { "physicsLayer": 8, "renderingLayerMask": 1, "applyToChildren": true, "childOverrides": [ { "childPath": "Hitbox", "physicsLayer": 11, "renderingLayerMask": 1 } ] }, "variants": [ { "variantId": 1, "variantName": "Armored", "inheritanceMode": "MaterialOnly", "preservation": { "preserveMeshCollider": true, "preserveRigidbody": true, "preservePhysicsLayers": true }, "tagOverrides": { "addTags": ["heavy", "armored"], "removeTags": [] } } ] } ], "routes": [ { "category": "Character", "relativePath": "Characters", "addressableLabel": "Pool/Characters" } ]}tag-registry.json (Export Format)
Section titled “tag-registry.json (Export Format)”{ "registryId": "default-tags", "version": "1.0.0", "tags": [ { "tagName": "interactable", "displayName": "Interactable Object", "description": "Can be interacted with by player", "category": "Functional", "editorColor": "#4CAF50", "isSystemTag": true, "requiredComponents": ["InteractableComponent"], "incompatibleTags": [] }, { "tagName": "solid", "displayName": "Solid Collider", "description": "Has physics collision enabled", "category": "Physics", "editorColor": "#2196F3", "isSystemTag": true, "requiredComponents": [], "incompatibleTags": ["trigger"] }, { "tagName": "trigger", "displayName": "Trigger Volume", "description": "Collider is set to trigger mode", "category": "Physics", "editorColor": "#FF9800", "isSystemTag": true, "requiredComponents": [], "incompatibleTags": ["solid"] } ], "labelGroups": [ { "groupName": "Combat Entities", "includedTags": ["hostile", "friendly", "animated"], "addressableLabel": "Pool/Combat" } ]}Validation Rules
Section titled “Validation Rules”Required Validations
Section titled “Required Validations”| Rule | Description | Severity |
|---|---|---|
| Default Variant | Each proto enum value must have a variant 0 prefab | Error |
| Prefab Reference | All registered prefabs must have valid GameObject reference | Error |
| Unique IDs | No duplicate prefab IDs in registry | Error |
| Fallback Chain | Fallback variants must exist | Warning |
| Orphaned Prefabs | Prefabs in routes not in registry | Info |
| Missing Routes | Categories without route mappings | Warning |
| Tag Existence | All applied tags must exist in TagRegistry | Error |
| Tag Compatibility | No incompatible tags on same prefab | Error |
| Required Components | Tags with requiredComponents must have those components | Error |
| Layer Validity | Physics layer index must be valid (0-31) | Error |
| Variant Preservation | Variants must not override preserved components | Warning |
| Collider Consistency | MeshCollider mesh should match base variant | Info |
| LOD Required | Animated/complex prefabs should have LOD configuration | Warning |
| LOD Distance Order | LOD distances must increase with each level | Error |
| Impostor Recommended | Heavy prefabs should use impostors for WebGL | Warning |
| Shadow LOD | Environment prefabs should limit shadow casting | Info |
Validation API
Section titled “Validation API”public class RegistryValidator{ public ValidationResult Validate(PrefabRegistry registry); public List<PrefabDefinition> FindOrphanedPrefabs(string routePath); public List<string> FindMissingEnumCoverage<TEnum>(PoolCategory category); public bool ValidateFallbackChain(PrefabDefinition prefab);
// Tag Validation public List<string> ValidateTags(PrefabDefinition prefab, PrefabTagRegistry tagRegistry); public List<string> FindIncompatibleTags(PrefabDefinition prefab, PrefabTagRegistry tagRegistry); public List<string> FindMissingRequiredComponents(PrefabDefinition prefab, PrefabTagRegistry tagRegistry);
// Layer Validation public bool ValidateLayerConfiguration(PrefabLayerSettings settings); public List<string> FindInvalidLayerReferences(PrefabRegistry registry);
// Variant Validation public List<string> ValidateVariantPreservation(VariantDefinition variant, GameObject basePrefab); public bool ValidateColliderConsistency(PrefabDefinition basePrefab, VariantDefinition variant);}
public class ValidationResult{ public bool IsValid; public List<ValidationIssue> Errors; public List<ValidationIssue> Warnings; public List<ValidationIssue> Info;}
public class ValidationIssue{ public string PrefabId; public string Rule; public string Message; public ValidationSeverity Severity; public string SuggestedFix; // Actionable fix suggestion}
public enum ValidationSeverity{ Error, // Must fix before build Warning, // Should fix, may cause issues Info // Informational, optional fix}Migration Path
Section titled “Migration Path”From Current PoolPrefabConfig
Section titled “From Current PoolPrefabConfig”- Create new
PrefabRegistryasset - Run migration script to copy entries
- Update
PoolServiceto check registry first, fallback to config - Deprecate direct
PoolPrefabConfigusage over time - Remove old config once all systems migrated
// Migration helperpublic static class PoolConfigMigration{ public static PrefabRegistry MigrateFromConfig(PoolPrefabConfig config) { var registry = ScriptableObject.CreateInstance<PrefabRegistry>();
foreach (var entry in config.characterPrefabs) { registry.prefabs.Add(new PrefabDefinition { prefabId = $"char_{entry.type.ToString().ToLower()}_v{entry.variantId}", category = PoolCategory.Character, typeId = (int)entry.type, variantId = entry.variantId, prefab = entry.prefab, isDefaultVariant = entry.variantId == 0 }); }
// ... repeat for other categories
return registry; }}Open Questions
Section titled “Open Questions”-
Addressables vs Direct References: Should we fully migrate to Addressables for all prefabs, or keep hybrid approach?
-
Variant Naming: Should variants be named (e.g., “armored”, “winter”) or just numbered?
- Decision: Use both -
variantIdfor internal lookups,variantNamefor display and debugging
- Decision: Use both -
-
LOD Support: Should the pool system handle LOD variants, or is that Unity’s LODGroup responsibility?
- Decision: Hybrid approach - use Unity LODGroup where available, but pool system manages LOD configuration, distance overrides, impostor generation, and WebGL-specific optimizations. See Section 7 for full LOD integration.
-
Async Loading UI: How to handle loading states when prefabs aren’t prewarmed?
-
Hot Reload: Should we support runtime registry updates for development?
-
Tag Inheritance: Should child prefabs automatically inherit parent tags, or should tags be explicitly defined?
-
Collider Baking: When a variant has a significantly different mesh, should we support optional collider rebaking with explicit opt-in?
-
Material Property Blocks: Should variants use MaterialPropertyBlocks for per-instance overrides, or create unique material instances?
-
Layer Synchronization: How to handle layer changes when prefabs are already instantiated in the world?
Future Extensions
Section titled “Future Extensions”9. Content Data Pipeline (Astro → Unity)
Section titled “9. Content Data Pipeline (Astro → Unity)”Concept: Use MDX files in Astro as the single source of truth for game data (harvest yields, crafting recipes, item stats). Astro build process exports JSON that Unity imports as ScriptableObjects.
flowchart LR
subgraph ASTRO["ASTRO (Source of Truth)"]
MDX["content/<br/>trees/oak.mdx<br/>trees/pine.mdx<br/>resources/wood.mdx<br/>resources/stone.mdx"]
FM["Frontmatter:<br/>harvestYield: 5<br/>respawnTime: 300<br/>requiredTool: axe"]
end
subgraph BUILD["Build Process"]
JSON["content-data.json"]
end
subgraph UNITY["UNITY (Runtime Consumer)"]
SO["Resources/Data/<br/>TreeData.asset<br/>RockData.asset<br/>HerbData.asset"]
GEN["Generated SO with:<br/>- harvestYield<br/>- respawnTime<br/>- requiredTool<br/>- dropTable[]<br/>- seasonalMods"]
end
MDX --> JSON
FM --> JSON
JSON --> SO
JSON --> GEN
MDX Content Schema (Astro)
Section titled “MDX Content Schema (Astro)”---# content/harvestables/trees/oak.mdxid: "env_tree_oak"displayName: "Oak Tree"category: "Environment"subCategory: "Trees"
harvest: baseYield: 5 yieldVariance: 2 # 3-7 logs per harvest resource: "wood_oak" toolRequired: "axe" toolTierMin: 1
timing: harvestDuration: 3.5 # seconds respawnTime: 300 # seconds
drops: - id: "wood_oak" chance: 1.0 min: 3 max: 7 - id: "acorn" chance: 0.15 min: 1 max: 2 - id: "bark_oak" chance: 0.3 min: 1 max: 1
seasonal: spring: { yieldMod: 1.0 } summer: { yieldMod: 1.2 } autumn: { yieldMod: 1.0, extraDrops: ["fallen_leaves"] } winter: { yieldMod: 0.6 }---
# Oak Tree
The mighty oak provides sturdy lumber for construction...10. Composite Prefab Architecture
Section titled “10. Composite Prefab Architecture”Concept: Composites are prefabs built from multiple sub-prefabs (base + attachments). A “mossy rock” is a Rock base with Moss attachment. Composites inherit the base’s physics while adding visual layers.
flowchart TB
subgraph SIMPLE["Simple Prefab"]
SP[Rock_v0<br/>└ Mesh<br/>└ Collider<br/>└ Material]
end
subgraph COMPOSITE["Composite Prefab"]
CP["Rock_Mossy_v0 (Composite)"]
BASE["├ Base: Rock_v0 ◄── Physics source<br/>│ └ Mesh<br/>│ └ MeshCollider (PRESERVED)"]
ATT["└ Attachments[]<br/> └ Moss_Patch_01<br/> └ Mesh (no collider)<br/> └ offset: (0, 0.2, 0)<br/> └ Moss_Patch_02<br/> └ Mesh<br/> └ offset: (0.3, 0.1, 0)"]
end
subgraph CATEGORIES["Composite Categories"]
C1["Environmental: Rock+Moss, Tree+Vines, Stump+Mushrooms"]
C2["Structural: Wall+Damage, Floor+Debris, Roof+Snow"]
C3["Interactive: Chest+Lock, Door+Hinges, Lever+Mechanism"]
C4["Character: Base+Armor, Base+Accessories, Base+Effects"]
end
Composite Definition Schema
Section titled “Composite Definition Schema”[Serializable]public class CompositeDefinition{ [Header("Identity")] public string compositeId; // "env_rock_mossy_v0" public string displayName; // "Mossy Boulder"
[Header("Base Prefab")] public string basePrefabId; // "env_rock_boulder_v0" - source of physics public bool inheritBaseTags = true; public bool inheritBaseData = true; // Links to HarvestableData, etc.
[Header("Attachments")] public CompositeAttachment[] attachments;
[Header("Composite Tags")] public string[] additionalTags; // ["mossy", "wet", "slippery"]
[Header("Data Overrides")] public DataOverride[] dataOverrides; // Override base harvestable data}
[Serializable]public class CompositeAttachment{ public string attachmentPrefabId; // "env_moss_patch_v0" public string attachPointName; // "MossPoint01" or null for offset
[Header("Transform")] public Vector3 localPosition; public Vector3 localRotation; public Vector3 localScale = Vector3.one;
[Header("Variation")] public bool randomRotationY = true; // Random Y rotation for variety public float scaleVariance = 0.1f; // ±10% scale variation
[Header("Conditions")] public string[] requiredConditions; // ["wet_biome", "shaded"] public float spawnChance = 1.0f; // 0-1, for conditional spawning}Future Implementation Phases
Section titled “Future Implementation Phases”| Phase | Feature | Status | Dependencies |
|---|---|---|---|
| Phase 7 | Content Data Pipeline | COMPLETE | Astro MDX schema, JSON export |
| Phase 8 | Composite Prefab System | COMPLETE | Variant Architecture (Phase 3) |
| Phase 9 | Astro ↔ Unity Sync | COMPLETE | CI/CD pipeline, content-data.json |
| Phase 10 | Composite Editor UI | COMPLETE | Bakery Integration (Phase 4) |
Related Documents
Section titled “Related Documents”- Game Design Document - Core game design documentation
- Pathfinding - Navigation and pathfinding systems
proto/rentearth/snapshot.proto- Proto definitions for entity typesproto/rentearth/pool.proto- Pool category definitions
Changelog
Section titled “Changelog”| Date | Version | Changes |
|---|---|---|
| 2025-12-08 | 0.14.0 | PHASE 10: Composite Editor UI: Created CompositeOracleRecipe.cs - full Bakery recipe for managing composite prefabs. Features: split-panel layout with composite list (category filtering, attachment count badges) and full editor panel. |
| 2025-12-08 | 0.13.0 | PHASE 9: Astro ↔ Unity Sync Pipeline: Created astro_sync.py - orchestrates content sync by calling Node.js export-game-data.mjs, then imports JSON to Unity asset files. |
| 2025-12-08 | 0.12.0 | PHASE 8: Composite Prefab System: Created composite prefab architecture in Assets/Scripts/Pool/Oracle/. |
| 2025-12-08 | 0.11.0 | PHASE 7: Content Data Pipeline (Astro → Unity): Created content collections for game data with Zod schemas. |
| 2025-12-08 | 0.10.0 | Unified Proto Generation + Zod Schemas: Rewrote proto_sync.py as single multi-platform generator. |
| 2025-12-08 | 0.9.0 | Server-Client Proto Integration: Added proto export/import buttons to PrefabOracleRecipe UI. |
| 2025-12-08 | 0.8.0 | PHASE 6 COMPLETE (Polish): Extended pool.proto with messages. Created ProtoSync.cs. |
| 2025-12-07 | 0.7.0 | PHASE 4 COMPLETE (Bakery Integration): Created PrefabOracleRecipe with 5-tab UI. |
| 2025-12-07 | 0.6.0 | IMPLEMENTATION COMPLETE (Phases 1-3): Created Pool Oracle system. |
| 2025-12-07 | 0.5.0 | Added Future Extensions: Content Data Pipeline, Composite Prefab Architecture. |
| 2025-12-07 | 0.4.0 | Added Bakery Sidebar Integration with badge counts. |
| 2025-12-07 | 0.3.0 | Added LOD Integration section with WebGL-specific optimizations. |
| 2025-12-07 | 0.2.0 | Added Tag & Label System, Unity Layer Configuration, Variant Architecture. |
| 2025-12-07 | 0.1.0 | Initial design document |