diff --git a/src/AcDream.App/Rendering/Wb/GlobalMeshBuffer.cs b/src/AcDream.App/Rendering/Wb/GlobalMeshBuffer.cs
index 3441c66a..3e2c78e2 100644
--- a/src/AcDream.App/Rendering/Wb/GlobalMeshBuffer.cs
+++ b/src/AcDream.App/Rendering/Wb/GlobalMeshBuffer.cs
@@ -1,3 +1,4 @@
+using AcDream.Content;
using Chorizite.Core.Render.Enums;
using Silk.NET.OpenGL;
using System;
diff --git a/src/AcDream.App/Rendering/Wb/ObjectMeshManager.cs b/src/AcDream.App/Rendering/Wb/ObjectMeshManager.cs
index 7d9368ef..c41ab98e 100644
--- a/src/AcDream.App/Rendering/Wb/ObjectMeshManager.cs
+++ b/src/AcDream.App/Rendering/Wb/ObjectMeshManager.cs
@@ -26,118 +26,6 @@ using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
namespace AcDream.App.Rendering.Wb {
- ///
- /// Vertex format for scenery mesh rendering: position, normal, UV.
- ///
- [StructLayout(LayoutKind.Sequential)]
- public struct VertexPositionNormalTexture {
- public Vector3 Position;
- public Vector3 Normal;
- public Vector2 UV;
-
- public static int Size => 8 * sizeof(float); // 3+3+2 = 8 floats = 32 bytes
-
- public VertexPositionNormalTexture(Vector3 position, Vector3 normal, Vector2 uv) {
- Position = position;
- Normal = normal;
- UV = uv;
- }
- }
-
- ///
- /// Staged data for a particle emitter to be created on the GL thread.
- ///
- public struct StagedEmitter {
- public ParticleEmitter Emitter;
- public uint PartIndex;
- public Matrix4x4 Offset;
- }
-
- ///
- /// CPU-side mesh data prepared on a background thread.
- /// Contains vertex data and per-batch index/texture info, but NO GPU resources.
- ///
- public class ObjectMeshData {
- public ulong ObjectId { get; set; }
- public bool IsSetup { get; set; }
- public VertexPositionNormalTexture[] Vertices { get; set; } = Array.Empty();
- public List Batches { get; set; } = new();
-
- ///
- /// #125 (2026-06-12): GL upload-retry counter. A failed
- /// (returns null from its
- /// catch) used to be dropped permanently — the staged item was consumed,
- /// no render data was produced, and the prepared data lingered in the CPU
- /// cache where PrepareMeshDataAsync's cache-hit short-circuit
- /// returned it without ever re-staging it for upload (session-sticky
- /// invisible mesh, one [wb-error] line). The drain loop now re-stages a
- /// failed upload for the NEXT frame up to times. The counter lives on the mesh-data object so
- /// it resets to 0 naturally whenever the id is re-prepared (fresh object),
- /// and bounds a deterministic GL failure to a few loud lines instead of a
- /// silent permanent drop OR an unbounded per-frame retry storm. Retail
- /// loads content synchronously and has no such failure mode — this
- /// converges our async pipeline toward that guarantee.
- ///
- public int UploadAttempts;
-
- /// For EnvCell: the geometry of the cell itself.
- public ObjectMeshData? EnvCellGeometry { get; set; }
-
- /// For Setup objects: parts with their local transforms.
- public List<(ulong GfxObjId, Matrix4x4 Transform)> SetupParts { get; set; } = new();
-
- /// Particle emitters from physics scripts.
- public List ParticleEmitters { get; set; } = new();
-
- /// Per-format texture atlas data (to be uploaded to GPU on main thread).
- public Dictionary<(int Width, int Height, TextureFormat Format), List> TextureBatches { get; set; } = new();
-
- /// Local bounding box.
- public BoundingBox BoundingBox { get; set; }
-
- /// Approximate center point used for depth sorting / transparency ordering.
- public Vector3 SortCenter { get; set; }
-
- /// DataID of a simpler GfxObj to use at long distance / low quality, or GfxObjDegradeInfo.
- public uint DIDDegrade { get; set; }
-
- /// Sphere used for mouse selection.
- public Sphere? SelectionSphere { get; set; }
-
- /// Edge line vertices for Environment wireframe rendering.
- public Vector3[] EdgeLines { get; set; } = Array.Empty();
- }
-
- ///
- /// CPU-side data for a single rendering batch (indices + texture reference).
- ///
- public class MeshBatchData {
- public ushort[] Indices { get; set; } = Array.Empty();
- public (int Width, int Height, TextureFormat Format) TextureFormat { get; set; }
- public TextureKey TextureKey { get; set; }
- public int TextureIndex { get; set; }
- public byte[] TextureData { get; set; } = Array.Empty();
- public PixelFormat? UploadPixelFormat { get; set; }
- public PixelType? UploadPixelType { get; set; }
- public DatReaderWriter.Enums.CullMode CullMode { get; set; }
- }
-
- ///
- /// CPU-side texture info for deduplication during background preparation.
- ///
- public class TextureBatchData {
- public TextureKey Key { get; set; }
- public byte[] TextureData { get; set; } = Array.Empty();
- public PixelFormat? UploadPixelFormat { get; set; }
- public PixelType? UploadPixelType { get; set; }
- public List Indices { get; set; } = new();
- public DatReaderWriter.Enums.CullMode CullMode { get; set; }
- public bool IsTransparent { get; set; }
- public bool IsAdditive { get; set; }
- public bool HasWrappingUVs { get; set; }
- }
-
///
/// GPU-side render data created on the main thread.
///
diff --git a/src/AcDream.App/Rendering/Wb/WbMeshAdapter.cs b/src/AcDream.App/Rendering/Wb/WbMeshAdapter.cs
index 8bbdd6bd..9440ba6c 100644
--- a/src/AcDream.App/Rendering/Wb/WbMeshAdapter.cs
+++ b/src/AcDream.App/Rendering/Wb/WbMeshAdapter.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using AcDream.Content;
using AcDream.Core.Meshing;
using AcDream.Core.Rendering;
using DatReaderWriter;
diff --git a/src/AcDream.Content/AcDream.Content.csproj b/src/AcDream.Content/AcDream.Content.csproj
index 768639e7..1abed36e 100644
--- a/src/AcDream.Content/AcDream.Content.csproj
+++ b/src/AcDream.Content/AcDream.Content.csproj
@@ -12,6 +12,14 @@
exact versions (mirror AcDream.App.csproj — keep in lockstep). -->
+
+
diff --git a/src/AcDream.Content/ObjectMeshData.cs b/src/AcDream.Content/ObjectMeshData.cs
new file mode 100644
index 00000000..8bba1e0c
--- /dev/null
+++ b/src/AcDream.Content/ObjectMeshData.cs
@@ -0,0 +1,125 @@
+using Chorizite.Core.Lib;
+using Chorizite.Core.Render.Enums;
+using DatReaderWriter.DBObjs;
+using DatReaderWriter.Types;
+using System;
+using System.Collections.Generic;
+using System.Numerics;
+using System.Runtime.InteropServices;
+using BoundingBox = Chorizite.Core.Lib.BoundingBox;
+using PixelFormat = Silk.NET.OpenGL.PixelFormat;
+using PixelType = Silk.NET.OpenGL.PixelType;
+
+namespace AcDream.Content;
+
+///
+/// Vertex format for scenery mesh rendering: position, normal, UV.
+///
+[StructLayout(LayoutKind.Sequential)]
+public struct VertexPositionNormalTexture {
+ public Vector3 Position;
+ public Vector3 Normal;
+ public Vector2 UV;
+
+ public static int Size => 8 * sizeof(float); // 3+3+2 = 8 floats = 32 bytes
+
+ public VertexPositionNormalTexture(Vector3 position, Vector3 normal, Vector2 uv) {
+ Position = position;
+ Normal = normal;
+ UV = uv;
+ }
+}
+
+///
+/// Staged data for a particle emitter to be created on the GL thread.
+///
+public struct StagedEmitter {
+ public ParticleEmitter Emitter;
+ public uint PartIndex;
+ public Matrix4x4 Offset;
+}
+
+///
+/// CPU-side mesh data prepared on a background thread.
+/// Contains vertex data and per-batch index/texture info, but NO GPU resources.
+///
+public class ObjectMeshData {
+ public ulong ObjectId { get; set; }
+ public bool IsSetup { get; set; }
+ public VertexPositionNormalTexture[] Vertices { get; set; } = Array.Empty();
+ public List Batches { get; set; } = new();
+
+ ///
+ /// #125 (2026-06-12): GL upload-retry counter. A failed
+ /// (returns null from its
+ /// catch) used to be dropped permanently — the staged item was consumed,
+ /// no render data was produced, and the prepared data lingered in the CPU
+ /// cache where PrepareMeshDataAsync's cache-hit short-circuit
+ /// returned it without ever re-staging it for upload (session-sticky
+ /// invisible mesh, one [wb-error] line). The drain loop now re-stages a
+ /// failed upload for the NEXT frame up to times. The counter lives on the mesh-data object so
+ /// it resets to 0 naturally whenever the id is re-prepared (fresh object),
+ /// and bounds a deterministic GL failure to a few loud lines instead of a
+ /// silent permanent drop OR an unbounded per-frame retry storm. Retail
+ /// loads content synchronously and has no such failure mode — this
+ /// converges our async pipeline toward that guarantee.
+ ///
+ public int UploadAttempts;
+
+ /// For EnvCell: the geometry of the cell itself.
+ public ObjectMeshData? EnvCellGeometry { get; set; }
+
+ /// For Setup objects: parts with their local transforms.
+ public List<(ulong GfxObjId, Matrix4x4 Transform)> SetupParts { get; set; } = new();
+
+ /// Particle emitters from physics scripts.
+ public List ParticleEmitters { get; set; } = new();
+
+ /// Per-format texture atlas data (to be uploaded to GPU on main thread).
+ public Dictionary<(int Width, int Height, TextureFormat Format), List> TextureBatches { get; set; } = new();
+
+ /// Local bounding box.
+ public BoundingBox BoundingBox { get; set; }
+
+ /// Approximate center point used for depth sorting / transparency ordering.
+ public Vector3 SortCenter { get; set; }
+
+ /// DataID of a simpler GfxObj to use at long distance / low quality, or GfxObjDegradeInfo.
+ public uint DIDDegrade { get; set; }
+
+ /// Sphere used for mouse selection.
+ public Sphere? SelectionSphere { get; set; }
+
+ /// Edge line vertices for Environment wireframe rendering.
+ public Vector3[] EdgeLines { get; set; } = Array.Empty();
+}
+
+///
+/// CPU-side data for a single rendering batch (indices + texture reference).
+///
+public class MeshBatchData {
+ public ushort[] Indices { get; set; } = Array.Empty();
+ public (int Width, int Height, TextureFormat Format) TextureFormat { get; set; }
+ public TextureKey TextureKey { get; set; }
+ public int TextureIndex { get; set; }
+ public byte[] TextureData { get; set; } = Array.Empty();
+ public PixelFormat? UploadPixelFormat { get; set; }
+ public PixelType? UploadPixelType { get; set; }
+ public DatReaderWriter.Enums.CullMode CullMode { get; set; }
+}
+
+///
+/// CPU-side texture info for deduplication during background preparation.
+///
+public class TextureBatchData {
+ public TextureKey Key { get; set; }
+ public byte[] TextureData { get; set; } = Array.Empty();
+ public PixelFormat? UploadPixelFormat { get; set; }
+ public PixelType? UploadPixelType { get; set; }
+ public List Indices { get; set; } = new();
+ public DatReaderWriter.Enums.CullMode CullMode { get; set; }
+ public bool IsTransparent { get; set; }
+ public bool IsAdditive { get; set; }
+ public bool HasWrappingUVs { get; set; }
+}