Set up DOTS + Netcode for Entities foundation

One-time stack setup per Docs/dots-setup-task.md (Unity 6.4.7 / 6000.4.7f1).
Packages: entities 6.4.0, entities.graphics 6.4.0, netcode 1.13.2, physics 1.4.6.

- Assets/_Project asmdef split: ProjectM.Simulation/Client/Server/Authoring (root ns ProjectM)
- GameBootstrap : ClientServerBootstrap; verified separate client + server worlds in Play Mode
- Gameplay subscene wired into SampleScene as a baking target
- Heartbeat component + Burst ISystem; EditMode smoke test green (1/1)
- In-repo Obsidian vault (Docs/Vault) incl. DR-001 (plain-Entities test over internal NetCodeTestWorld)
- Portable .mcp.json (basic-memory + serena via ${CLAUDE_PROJECT_DIR}); CLAUDE.md conventions
- .gitignore for DOTS baking cache + machine-local config

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Luis Gonzalez
2026-05-29 22:06:44 -07:00
parent 25e3493e75
commit 99d8d2d2a9
74 changed files with 1609 additions and 36 deletions
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: eac3f5ab514a741ff9af8d4891f18ed4
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1d1aa4405eacf4870a4a196c1003df87
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,19 @@
{
"name": "ProjectM.Authoring",
"rootNamespace": "ProjectM.Authoring",
"references": [
"ProjectM.Simulation",
"Unity.Entities",
"Unity.Mathematics",
"Unity.NetCode"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": true,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 58fe4442d47fb4ab1a1de5ec2fa1bc4a
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ea6b9bcd5e8084ceca808056442d4634
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,22 @@
{
"name": "ProjectM.Client",
"rootNamespace": "ProjectM.Client",
"references": [
"ProjectM.Simulation",
"Unity.Entities",
"Unity.Collections",
"Unity.Mathematics",
"Unity.Burst",
"Unity.NetCode",
"Unity.Entities.Graphics"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": true,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 6b021a17bba824c5799f8f2c87ada5e3
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 03b6366760d1f4cbc88f29039a6e70cf
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,21 @@
{
"name": "ProjectM.Server",
"rootNamespace": "ProjectM.Server",
"references": [
"ProjectM.Simulation",
"Unity.Entities",
"Unity.Collections",
"Unity.Mathematics",
"Unity.Burst",
"Unity.NetCode"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": true,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 634384718530e45608c20d4952b5acb2
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 090d9f42a3883497c9be2006e4fa486f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,24 @@
using Unity.Entities;
using Unity.NetCode;
using UnityEngine.Scripting;
namespace ProjectM.Simulation
{
/// <summary>
/// Custom Netcode for Entities bootstrap. Subclassing <see cref="ClientServerBootstrap"/>
/// gives an explicit hook to customize world creation, tick rate, and auto-connect.
/// For now it reproduces the default behavior: create separate client and server worlds
/// based on the Multiplayer PlayMode Tools settings, without auto-connecting.
/// </summary>
[Preserve]
public class GameBootstrap : ClientServerBootstrap
{
public override bool Initialize(string defaultWorldName)
{
// 0 = do not auto-connect; worlds are still created. Set a port later to auto-connect.
AutoConnectPort = 0;
CreateDefaultClientServerWorlds();
return true;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9f727ee3d680248a8b0e2e55d234ab5f
@@ -0,0 +1,13 @@
using Unity.Entities;
namespace ProjectM.Simulation
{
/// <summary>
/// Trivial unmanaged component used by the setup smoke test to prove the ECS
/// compile + source-gen + tick path works. Safe to delete once real gameplay exists.
/// </summary>
public struct Heartbeat : IComponentData
{
public int Tick;
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: a0347bd6d99db438f85ec1985b7f81e1
@@ -0,0 +1,23 @@
using Unity.Burst;
using Unity.Entities;
namespace ProjectM.Simulation
{
/// <summary>
/// Smoke-test system: increments every <see cref="Heartbeat"/> once per tick.
/// Default (no <c>[WorldSystemFilter]</c>) so it runs in the SimulationSystemGroup of
/// every world. Burst-compiled and unmanaged (<see cref="ISystem"/>) per DOTS convention.
/// </summary>
[BurstCompile]
public partial struct HeartbeatSystem : ISystem
{
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
foreach (var heartbeat in SystemAPI.Query<RefRW<Heartbeat>>())
{
heartbeat.ValueRW.Tick++;
}
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 78349f49733934f348acb488689fc9ec
@@ -0,0 +1,21 @@
{
"name": "ProjectM.Simulation",
"rootNamespace": "ProjectM.Simulation",
"references": [
"Unity.Entities",
"Unity.Collections",
"Unity.Mathematics",
"Unity.Burst",
"Unity.Physics",
"Unity.NetCode"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": true,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 113bb240387ed4cd397f37597898de62
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b7f5b732d379744829318dee89eaf7bc
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+125
View File
@@ -0,0 +1,125 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!29 &1
OcclusionCullingSettings:
m_ObjectHideFlags: 0
serializedVersion: 2
m_OcclusionBakeSettings:
smallestOccluder: 5
smallestHole: 0.25
backfaceThreshold: 100
m_SceneGUID: 00000000000000000000000000000000
m_OcclusionCullingData: {fileID: 0}
--- !u!104 &2
RenderSettings:
m_ObjectHideFlags: 0
serializedVersion: 10
m_Fog: 0
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
m_FogMode: 3
m_FogDensity: 0.01
m_LinearFogStart: 0
m_LinearFogEnd: 300
m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
m_AmbientIntensity: 1
m_AmbientMode: 0
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0}
m_HaloStrength: 0.5
m_FlareStrength: 1
m_FlareFadeSpeed: 3
m_HaloTexture: {fileID: 0}
m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
m_DefaultReflectionMode: 0
m_DefaultReflectionResolution: 128
m_ReflectionBounces: 1
m_ReflectionIntensity: 1
m_CustomReflection: {fileID: 0}
m_Sun: {fileID: 0}
m_UseRadianceAmbientProbe: 0
--- !u!157 &3
LightmapSettings:
m_ObjectHideFlags: 0
serializedVersion: 13
m_BakeOnSceneLoad: 0
m_GISettings:
serializedVersion: 2
m_BounceScale: 1
m_IndirectOutputScale: 1
m_AlbedoBoost: 1
m_EnvironmentLightingMode: 0
m_EnableBakedLightmaps: 1
m_EnableRealtimeLightmaps: 0
m_LightmapEditorSettings:
serializedVersion: 12
m_Resolution: 2
m_BakeResolution: 40
m_AtlasSize: 1024
m_AO: 0
m_AOMaxDistance: 1
m_CompAOExponent: 1
m_CompAOExponentDirect: 0
m_ExtractAmbientOcclusion: 0
m_Padding: 2
m_LightmapParameters: {fileID: 0}
m_LightmapsBakeMode: 1
m_TextureCompression: 1
m_ReflectionCompression: 2
m_MixedBakeMode: 2
m_BakeBackend: 2
m_PVRSampling: 1
m_PVRDirectSampleCount: 32
m_PVRSampleCount: 512
m_PVRBounces: 2
m_PVREnvironmentSampleCount: 256
m_PVREnvironmentReferencePointCount: 2048
m_PVRFilteringMode: 1
m_PVRDenoiserTypeDirect: 1
m_PVRDenoiserTypeIndirect: 1
m_PVRDenoiserTypeAO: 1
m_PVRFilterTypeDirect: 0
m_PVRFilterTypeIndirect: 0
m_PVRFilterTypeAO: 0
m_PVREnvironmentMIS: 1
m_PVRCulling: 1
m_PVRFilteringGaussRadiusDirect: 1
m_PVRFilteringGaussRadiusIndirect: 1
m_PVRFilteringGaussRadiusAO: 1
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
m_PVRFilteringAtrousPositionSigmaIndirect: 2
m_PVRFilteringAtrousPositionSigmaAO: 1
m_ExportTrainingData: 0
m_TrainingDataDestination: TrainingData
m_LightProbeSampleCountMultiplier: 4
m_LightingDataAsset: {fileID: 20201, guid: 0000000000000000f000000000000000, type: 0}
m_LightingSettings: {fileID: 0}
--- !u!196 &4
NavMeshSettings:
serializedVersion: 2
m_ObjectHideFlags: 0
m_BuildSettings:
serializedVersion: 3
agentTypeID: 0
agentRadius: 0.5
agentHeight: 2
agentSlope: 45
agentClimb: 0.4
ledgeDropHeight: 0
maxJumpAcrossDistance: 0
minRegionArea: 2
manualCellSize: 0
cellSize: 0.16666667
manualTileSize: 0
tileSize: 256
buildHeightMesh: 0
maxJobWorkers: 0
preserveTilesOutsideBounds: 0
debug:
m_Flags: 0
m_NavMeshData: {fileID: 0}
--- !u!1660057539 &9223372036854775807
SceneRoots:
m_ObjectHideFlags: 0
m_Roots: []
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 9dc8ce2e486074792932d618c976e312
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 63a9defe75b7a4f95bbdc6993c9f0153
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0510e8296168e47ffbccd9777823a996
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,40 @@
using NUnit.Framework;
using Unity.Entities;
using ProjectM.Simulation;
namespace ProjectM.Tests
{
/// <summary>
/// Setup smoke test: proves the DOTS toolchain compiles, source-generates, and ticks.
/// Boots a bare ECS world, registers <see cref="HeartbeatSystem"/> in the
/// SimulationSystemGroup, and asserts the component advances once per group update.
/// (Netcode client/server world boot is covered separately by the §3 Play Mode check.)
/// </summary>
public class HeartbeatSystemTests
{
[Test]
public void Heartbeat_Advances_Once_Per_SimulationGroup_Tick()
{
using var world = new World("HeartbeatTestWorld");
var simulationGroup = world.GetOrCreateSystemManaged<SimulationSystemGroup>();
var heartbeatSystem = world.GetOrCreateSystem<HeartbeatSystem>();
simulationGroup.AddSystemToUpdateList(heartbeatSystem);
simulationGroup.SortSystems();
var entityManager = world.EntityManager;
var entity = entityManager.CreateEntity(typeof(Heartbeat));
Assert.AreEqual(0, entityManager.GetComponentData<Heartbeat>(entity).Tick,
"Heartbeat should start at 0.");
const int ticks = 8;
for (int i = 0; i < ticks; i++)
{
simulationGroup.Update();
}
Assert.AreEqual(ticks, entityManager.GetComponentData<Heartbeat>(entity).Tick,
"Heartbeat.Tick should advance exactly once per SimulationSystemGroup update.");
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 74e3326af426947cfaff903aa94ed9f0
@@ -0,0 +1,27 @@
{
"name": "ProjectM.Tests.EditMode",
"rootNamespace": "ProjectM.Tests",
"references": [
"ProjectM.Simulation",
"Unity.Entities",
"Unity.Collections",
"Unity.Mathematics",
"UnityEngine.TestRunner",
"UnityEditor.TestRunner"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": true,
"precompiledReferences": [
"nunit.framework.dll"
],
"autoReferenced": false,
"defineConstraints": [
"UNITY_INCLUDE_TESTS"
],
"versionDefines": [],
"noEngineReferences": false
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 1fc76dfd7bbd349d188d6561a2e30e0d
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: