From 8aed3363401be5f90f7000874e575f7d2df18fcb Mon Sep 17 00:00:00 2001 From: Luis Gonzalez Date: Sat, 6 Jun 2026 15:06:26 -0700 Subject: [PATCH] Docs: align CLAUDE.md + vault to scene-split / Automation / Saves / UITK CLAUDE.md: rewrite Bootstrap&worlds (scene split + on-demand frontend), add M7 Automation + disk-persistence + UITK HUD bullets, new build-gotchas, note new folders fit existing asmdefs. Vault: Milestones (M6 + polish pass -> Done; HUD + cleanup rows), Backlog (ConnectionUI done + cleanup notes), Home/Systems_Index dates. Add the 3 prior session logs + DR-019/020/021 + the 2026-06-06 cleanup log + screenshots. Co-Authored-By: Claude Opus 4.8 --- Assets/Screenshots/Cleanup_PlaySmoke.png | 3 + Assets/Screenshots/Cleanup_PlaySmoke.png.meta | 117 ++++++++++++++++++ Assets/Screenshots/HudRework_Base.png | 3 + Assets/Screenshots/HudRework_Base.png.meta | 117 ++++++++++++++++++ Assets/Screenshots/HudRework_BuildMode.png | 3 + .../Screenshots/HudRework_BuildMode.png.meta | 117 ++++++++++++++++++ Assets/Screenshots/MainMenu_Boot.png | 3 + Assets/Screenshots/MainMenu_Boot.png.meta | 117 ++++++++++++++++++ Assets/Screenshots/MainMenu_Single_InGame.png | 3 + .../MainMenu_Single_InGame.png.meta | 117 ++++++++++++++++++ Assets/Screenshots/MainMenu_WithContinue.png | 3 + .../MainMenu_WithContinue.png.meta | 117 ++++++++++++++++++ CLAUDE.md | 18 ++- Docs/Vault/00_Home/Home.md | 6 +- Docs/Vault/02_Game_Design/Systems_Index.md | 13 +- Docs/Vault/06_Roadmap/Backlog.md | 31 ++++- Docs/Vault/06_Roadmap/Milestones.md | 11 +- ...Game_Infrastructure_Menu_Settings_Saves.md | 82 ++++++++++++ ...2026-06-05_HUD_Rework_UITK_BuildPalette.md | 41 ++++++ .../2026/2026-06-05_M7_Automation.md | 49 ++++++++ .../2026/2026-06-06_Cleanup_Alignment.md | 53 ++++++++ ...-019_Frontend_Menu_Settings_Saves_Build.md | 41 ++++++ .../DR-020_M7_Automation_Production_Chains.md | 52 ++++++++ .../DR-021_HUD_UITK_BuildPalette.md | 44 +++++++ 24 files changed, 1144 insertions(+), 17 deletions(-) create mode 100644 Assets/Screenshots/Cleanup_PlaySmoke.png create mode 100644 Assets/Screenshots/Cleanup_PlaySmoke.png.meta create mode 100644 Assets/Screenshots/HudRework_Base.png create mode 100644 Assets/Screenshots/HudRework_Base.png.meta create mode 100644 Assets/Screenshots/HudRework_BuildMode.png create mode 100644 Assets/Screenshots/HudRework_BuildMode.png.meta create mode 100644 Assets/Screenshots/MainMenu_Boot.png create mode 100644 Assets/Screenshots/MainMenu_Boot.png.meta create mode 100644 Assets/Screenshots/MainMenu_Single_InGame.png create mode 100644 Assets/Screenshots/MainMenu_Single_InGame.png.meta create mode 100644 Assets/Screenshots/MainMenu_WithContinue.png create mode 100644 Assets/Screenshots/MainMenu_WithContinue.png.meta create mode 100644 Docs/Vault/07_Sessions/2026/2026-06-05_Game_Infrastructure_Menu_Settings_Saves.md create mode 100644 Docs/Vault/07_Sessions/2026/2026-06-05_HUD_Rework_UITK_BuildPalette.md create mode 100644 Docs/Vault/07_Sessions/2026/2026-06-05_M7_Automation.md create mode 100644 Docs/Vault/07_Sessions/2026/2026-06-06_Cleanup_Alignment.md create mode 100644 Docs/Vault/07_Sessions/_Decisions/DR-019_Frontend_Menu_Settings_Saves_Build.md create mode 100644 Docs/Vault/07_Sessions/_Decisions/DR-020_M7_Automation_Production_Chains.md create mode 100644 Docs/Vault/07_Sessions/_Decisions/DR-021_HUD_UITK_BuildPalette.md diff --git a/Assets/Screenshots/Cleanup_PlaySmoke.png b/Assets/Screenshots/Cleanup_PlaySmoke.png new file mode 100644 index 000000000..5b60c78ef --- /dev/null +++ b/Assets/Screenshots/Cleanup_PlaySmoke.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fee0bf191e5e012f24d2129ad495a6f63a30c75073908fca5e9fc3e1c509ece9 +size 425757 diff --git a/Assets/Screenshots/Cleanup_PlaySmoke.png.meta b/Assets/Screenshots/Cleanup_PlaySmoke.png.meta new file mode 100644 index 000000000..3da8f073f --- /dev/null +++ b/Assets/Screenshots/Cleanup_PlaySmoke.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 95410c4726aa779468af6052842fb68c +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Screenshots/HudRework_Base.png b/Assets/Screenshots/HudRework_Base.png new file mode 100644 index 000000000..9744883c0 --- /dev/null +++ b/Assets/Screenshots/HudRework_Base.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:edd8922b9f640c3f482b1062c903dde96793c768b25c009a728af48489b580bf +size 347345 diff --git a/Assets/Screenshots/HudRework_Base.png.meta b/Assets/Screenshots/HudRework_Base.png.meta new file mode 100644 index 000000000..31bc0f41c --- /dev/null +++ b/Assets/Screenshots/HudRework_Base.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: ce1677d1cad4db94e9c171c518fe2e64 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Screenshots/HudRework_BuildMode.png b/Assets/Screenshots/HudRework_BuildMode.png new file mode 100644 index 000000000..732dabad3 --- /dev/null +++ b/Assets/Screenshots/HudRework_BuildMode.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c3f03f5db9faefb0d9aa6048cdfca0c5ccef2732359f7642863f7ccc690dbf1b +size 347428 diff --git a/Assets/Screenshots/HudRework_BuildMode.png.meta b/Assets/Screenshots/HudRework_BuildMode.png.meta new file mode 100644 index 000000000..cbc51f179 --- /dev/null +++ b/Assets/Screenshots/HudRework_BuildMode.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 59e0ee081a3be9448b9b7229d1933d9a +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Screenshots/MainMenu_Boot.png b/Assets/Screenshots/MainMenu_Boot.png new file mode 100644 index 000000000..b3686cf5a --- /dev/null +++ b/Assets/Screenshots/MainMenu_Boot.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e226c1f5b5b14f1e810ee15c3e9e256e10b6dba8ff2eb7df732188153fd4c9a1 +size 60317 diff --git a/Assets/Screenshots/MainMenu_Boot.png.meta b/Assets/Screenshots/MainMenu_Boot.png.meta new file mode 100644 index 000000000..40473956f --- /dev/null +++ b/Assets/Screenshots/MainMenu_Boot.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 940a4cc964593804a9d515f03152ee03 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Screenshots/MainMenu_Single_InGame.png b/Assets/Screenshots/MainMenu_Single_InGame.png new file mode 100644 index 000000000..6d033645a --- /dev/null +++ b/Assets/Screenshots/MainMenu_Single_InGame.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:174178af5d54bde69cff57d3a5dd8e9ef223548457f22eef9bee2476d5b0ac1d +size 300419 diff --git a/Assets/Screenshots/MainMenu_Single_InGame.png.meta b/Assets/Screenshots/MainMenu_Single_InGame.png.meta new file mode 100644 index 000000000..24164972c --- /dev/null +++ b/Assets/Screenshots/MainMenu_Single_InGame.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: 65cd5901bad28064aa4e5c00537e16d9 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Screenshots/MainMenu_WithContinue.png b/Assets/Screenshots/MainMenu_WithContinue.png new file mode 100644 index 000000000..09a27408a --- /dev/null +++ b/Assets/Screenshots/MainMenu_WithContinue.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2067757a994b30741e0b7220eb5b73ccee128de56eb6ceeda8f3562b01b8d0f +size 61613 diff --git a/Assets/Screenshots/MainMenu_WithContinue.png.meta b/Assets/Screenshots/MainMenu_WithContinue.png.meta new file mode 100644 index 000000000..f13653fa4 --- /dev/null +++ b/Assets/Screenshots/MainMenu_WithContinue.png.meta @@ -0,0 +1,117 @@ +fileFormatVersion: 2 +guid: b9b3ab80ee93ca44cb6cdeba33aa46a8 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/CLAUDE.md b/CLAUDE.md index 2279bb997..58011fb9c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -33,6 +33,7 @@ Root namespace: **`ProjectM`**. Code lives under `Assets/_Project/Scripts/` in f - **Simulation** = components + systems shared by both worlds (most gameplay). **Client/Server** = world-specific. **Authoring** = `…Authoring` MonoBehaviours + `Baker`. - Other folders: `Assets/_Project/Subscenes/` (baked entity subscenes), `Assets/_Project/Prefabs/`, `Assets/_Project/Tests/EditMode/`. +- Feature folders added since (`Client/UI`, `Client/Settings`, `Server/Automation`, `Server/Persistence`, `Simulation/Automation`, `Simulation/Persistence`) live **inside the existing four asmdefs — no new assemblies**. ## Build gotchas (distilled) @@ -65,6 +66,7 @@ Long-form originals + the milestone each came from: `Docs/Vault/_Meta/CLAUDE_Bui - **Cooldown/spawn "next tick" sentinels:** route every stored tick through **`TickUtil.NonZero(...)`** (a computed `ServerTick+delay` can wrap to 0, the "ready" sentinel) and compare with `NetworkTick.IsNewerThan` / `.TicksSince`, **never** raw `uint <` / subtraction. - **`GhostRelevancy` for region splits:** use `GhostRelevancyMode.SetIsIrrelevant` (not `SetIsRelevant`) so untagged/global ghosts stay relevant for free — only enumerate cross-region ghosts to hide. `RegionTag{byte Region}` is **server-only, NOT a `[GhostField]`** (server decides relevancy; client just gains/loses ghosts). `RelevantGhostForConnection{int Connection (=NetworkId.Value); int Ghost (=GhostInstance.ghostId)}`. - **Shared GLOBAL state (cycle phase, resource ledger, goal meter) rides an UNTAGGED ghost**, never a region-tagged one (`SetIsIrrelevant` would hide it cross-region). Resolve a ledger buffer via a DISTINCT tag (`ResourceLedger`), **never `GetSingleton`** when a second `StorageEntry` buffer exists elsewhere → "multiple instances" throw. +- **Frontend world lifecycle (menu → on-demand worlds) ★:** `CreateLocalWorld` is `internal` in 1.13.2 — use the public `CreateClientWorld` / `CreateServerWorld` (they register the `ServerWorld` / `ClientWorld` statics the connection UI/overlay read); for the menu world use `DefaultWorldInitialization.Initialize(name, false)` (or `return false` from `GameBootstrap.Initialize`). **Never dispose/create worlds inside an ECS system** — run all create/dispose/scene-load on a frame-boundary coroutine (`SessionRunner`, `DontDestroyOnLoad`). The gameplay subscene streams into an on-demand world ONLY if a netcode world is the `DefaultGameObjectInjectionWorld` at `LoadScene` time (dispose the menu world first → set the default to the server world → `LoadScene(Game)`). See [[DR-019_Frontend_Menu_Settings_Saves_Build]]. ### Physics & character controller - **Unity Physics 1.x bakes built-in `UnityEngine` colliders + `Rigidbody`** (the Physics-0.x `PhysicsShapeAuthoring`/`PhysicsBodyAuthoring` are gone). Static collider (no Rigidbody) → baked into the subscene PhysicsWorld, deterministic, no replication. `Rigidbody.FreezeRotation` is **NOT** honored by the baker — zero angular velocity + write rotation each tick, or set `PhysicsMass.InverseInertia = float3.zero`. @@ -78,17 +80,21 @@ Long-form originals + the milestone each came from: `Docs/Vault/_Meta/CLAUDE_Bui - **`PlacedStructure{[GhostField] byte Type; int2 Cell (server-only); uint NextTick; uint LastProcessedTick}`** on an ownerless interpolated ghost. **Bake the two tick fields now** (turret reuses `NextTick` as fire cooldown; they're the offline-catch-up linchpin). Only `Type` replicates (client derives `Cell` via `BaseGridMath.WorldToCell`). Data-driven `StructureCatalog` buffer. **Occupancy is DERIVED** by scanning live structure ghosts into a Temp `NativeHashSet`, never a mutable buffer on the baked `BaseAnchor`. - **Co-op placement atomicity:** commit the `StorageMath.Withdraw` + cell-reservation **in-place inside the RPC foreach** (only `Instantiate` goes through the ECB) so two same-tick requests for one cell can't both pass. - **Buildable turret = hitscan = reversed `EnemyAISystem`:** nearest living Husk in-region within Range, on `NextTick` cooldown append a direct `DamageEvent{Damage, SourceNetworkId=-1}` → reuses `HealthApplyDamageSystem`. No projectile → no tunnelling, no team model. -- **Resource-gated ability tiers reuse `StatModifier`** — grow ONE `StatModifier{Target=Damage, Op=PercentAdd, SourceId=}` (replace-by-SourceId so the buffer stays bounded); `StatRecomputeSystem` folds it into `EffectiveAbilityStats` on both worlds. `GoalProgress{[GhostField] int Charge, Target}` lives on the global CycleDirector ghost. **Disk-persistence deferred to post-M7** — freeze the save schema + bake structure tick fields now so it's additive. +- **Resource-gated ability tiers reuse `StatModifier`** — grow ONE `StatModifier{Target=Damage, Op=PercentAdd, SourceId=}` (replace-by-SourceId so the buffer stays bounded); `StatRecomputeSystem` folds it into `EffectiveAbilityStats` on both worlds. `GoalProgress{[GhostField] int Charge, Target}` lives on the global CycleDirector ghost. **Disk persistence shipped** — see the Automation + persistence bullets below. +- **M7 Automation (server-only, never predicted) ★:** `Harvester` / `Conveyor` / `Fabricator` are buildable machines on the same `PlacedStructure` ghost; each stores `PeriodTicks` + **server-only** `MachineInput` / `MachineOutput` buffers (NOT `[GhostField]`). Production runs in the plain server `SimulationSystemGroup` `[UpdateAfter(PredictedSimulationSystemGroup)]` (Harvester→Conveyor→Fabricator) and replicates only via the global ledger + `PlacedStructure`. Deterministic catch-up via `ProductionMath.CyclesDue` (**lower-bound 0, never 1** — a `1` premature-mints a restored `remaining==0` machine; period-0 guarded). Byte-only pure math (`ProductionMath` / `ConveyorMath.ResolveMoves` / `MachineSlotMath`) is EditMode-tested; `ConveyorMath` is order-independent (snapshot → stable-sort by `CellKey` → at-most-one destination claim → losers stall, no loss). `RuntimePlacedTag` marks player-built machines for the save-scan; `BuildPlaceSystem` stamps `LastProcessedTick=0` so runtime-placed machines hit `NeedsInit`. See [[DR-020_M7_Automation_Production_Chains]]. +- **Disk persistence (`SaveData`, single-slot atomic JSON at `persistentDataPath`) ★:** versioned, null on bad version, schema **additive** (bump the version, don't break it). **Born-correct load** — `CycleDirectorSpawnSystem` applies a staged `PendingSave` AT SPAWN so the director ghost never replicates a default first. Autosave on the Siege→Calm checkpoint + on quit-to-menu (`WorldLauncher.TrySaveFromServer`, host-only); `BaseRestoreSystem` replays saved structures **charge-free** with epoch-independent REMAINING-tick cooldowns + re-tags them. Shared `SaveStructureScan.Collect` (autosave + quit use ONE scan path). See [[DR-019_Frontend_Menu_Settings_Saves_Build]]. ### Presentation / juice / VFX - **All juice/HUD = client-only managed `SystemBase` in `PresentationSystemGroup`** (once/frame, no rollback double-fire) that OBSERVES replicated state, never mutates the sim. Read ECS via `SystemAPI.Query` in `OnUpdate` + `EntityManager.CompleteDependencyBeforeRO()` — NOT a MonoBehaviour `LateUpdate` (job-safety throw). `Entity` is a stable client dict key for a ghost's lifetime — **prune the cache each frame** (a pruned enemy = a kill → death VFX); **never `DestroyEntity` a ghost from the client** (`GhostDespawnSystem` owns despawn). Hit-stop = a camera punch, **never `Time.timeScale`** (corrupts the deterministic sim). -- **Asset-free presentation:** procedural `AudioClip.Create` SFX; runtime `ParticleSystem` pool (Sprites/Default + HDR start color); code-built uGUI HUD (`RawImage` over `Texture2D.whiteTexture`, legacy `Text` + `LegacyRuntime.ttf`). Edit a prefab asset's component in code via `PrefabUtility.LoadPrefabContents` → modify → **`SaveAsPrefabAsset(root, path)`** → `UnloadPrefabContents`. Watch **shared-material bleed** when re-tinting. ACES tonemapping needs URP color grading mode = HDR (`m_ColorGradingMode=1`). +- **Asset-free presentation:** procedural `AudioClip.Create` SFX; runtime `ParticleSystem` pool (Sprites/Default + HDR start color); code-built **UI Toolkit** HUD / menus (runtime `UIDocument` + shared `RuntimePanelSettings`; see the UITK bullet below). Edit a prefab asset's component in code via `PrefabUtility.LoadPrefabContents` → modify → **`SaveAsPrefabAsset(root, path)`** → `UnloadPrefabContents`. Watch **shared-material bleed** when re-tinting. ACES tonemapping needs URP color grading mode = HDR (`m_ColorGradingMode=1`). - **Prototype glue lives in `ProjectM.Client` as MonoBehaviours:** `PrototypeCameraRig` (player-following ARPG cam), `VFXConfig` (static `Instance` + prefab fields bridging authored VFX to the managed `CombatFeedbackSystem`; keep a procedural fallback). A **static presentation bridge must reset on play-enter** via `[RuntimeInitializeOnLoadMethod(SubsystemRegistration)]` (statics survive fast-enter-playmode reloads → stale flash). +- **UITK HUD + menus (in-game UI, on UI Toolkit) ★:** `MenuUi` owns the shared palette + element factories + `PanelSettings` / `EventSystem` plumbing and the **canonical `Round`/`Border` helpers**; `HudUi` is a thin extension (bars / labels). `HudSystem` is a `PresentationSystemGroup` observe-only `SystemBase` owning a runtime `UIDocument` (`sortingOrder 50`, behind the pause overlay's 100); it builds the tree on the first frame `rootVisualElement != null`, root `pickingMode = Ignore` so the HUD never eats world clicks (only palette buttons opt back in). **Runtime UITK needs a `PanelSettings` WITH a `themeStyleSheet`** (a `.tss` importing `unity-theme://default`) **AND** an `EventSystem` + `InputSystemUIInputModule` (Input System project) or buttons are silently dead. The **build palette** (lazy-built from the client `StructureCatalog`) drives click-to-place: ground-ghost preview (green/red via `BuildPreviewMath`, the client mirror of the server legality check), left-click places via the `BuildPlaceRequest` RPC, right-click/Esc cancels, `[`/`]`/R rotates a conveyor; `Fire` is suppressed while build mode is active. See [[DR-021_HUD_UITK_BuildPalette]]. ### Art import (HDRP store packs → URP) - BefourStudios art is **HDRP-authored** → magenta under URP 17.4 + Entities Graphics. **Convert, don't switch pipelines** (HDRP breaks Entities Graphics). Re-author to stock URP/Lit via `Assets/_Project/Scripts/Editor/EnvArtTools.cs` (menu `ProjectM/Art/1. Convert Curated Env Materials`). Synty art is **URP-native — no conversion**. - **A dark-lit screenshot MASKS material bugs — verify material *values*.** Always `shader.GetPropertyType(idx)`-guard before `GetColor`/`GetFloat`/`GetTexture` (`S_General`'s `_BaseColorMultiply` is a float; `GetColor` on it returns black). Gate source emission on the `_Emissive` flag AND a fixture name. Keep converted env metallic low (0.1–0.2). - **`VolumeProfile.Add()` does NOT persist** (serializes `{fileID:0}` on save) — use `AssetDatabase.AddObjectToAsset(component, profile)` + `SaveAssets`, verify on disk. +- **A reverted engine/URP upgrade can stamp `UniversalRenderPipelineGlobalSettings.asset` with `m_AssetVersion` AHEAD of the package's `k_LastVersion`** (here 11 > 10, from the reverted 6.6 alpha). URP migrates **forward only**, so `URPPreprocessBuild` rejects the "from-the-future" asset (*"not at last version"*) — **blocks player builds but NOT editor Play** (lurks until the first build). Fix: reflection-set `m_AssetVersion` back to `k_LastVersion`, `SetDirty` + `SaveAssets` (data already renders under the current package, only the stamp is wrong). - **`LocalTransform.FromPosition()` resets Scale=1** — server spawners must read the prefab's baked `LocalTransform` and override only Position (Scale is a replicated `[GhostField]` → consistent-but-wrong). - **Static decor → gameplay subscene** (Entities Graphics renders only baked/EG-spawned entities); **strip colliders from cosmetic props** (else they bake into the PhysicsWorld the CC sweeps), no `GhostAuthoring` on scenery. Cosmetic SampleScene GameObjects (classic URP, `SyntyWorld` root) render via classic URP and their colliders are **inert to the DOTS PhysicsWorld** — no stripping needed there. To swap a subscene object's visual while keeping collision: disable the MeshRenderer, keep the collider. - **A GA "projectile" prefab self-propels** (non-kinematic `Rigidbody` + collider + `ProjectileMoveScript`) — strip to particles before `Start` (`CombatFeedbackSystem.StripCosmetic`). Verify a prefab's *components*, not its name. @@ -99,7 +105,7 @@ Long-form originals + the milestone each came from: `Docs/Vault/_Meta/CLAUDE_Bui - **Cursor/reticle = client `PresentationSystemGroup` `SystemBase` (`AimReticleSystem`) that OBSERVES.** Re-raycast the KBM ground point INSIDE that system (PresentationSystemGroup runs after the follow-cam's LateUpdate) — latching from the gather drifts a frame behind. Hardware cursor hidden while aiming + focused, restored on focus-loss/`OnDestroy`. ### MCP / editor workflow ★ -- **Edit Assets `.cs` ONLY via MCP `apply_text_edits` / `create_script`** (Unity's scripting pipeline) — the raw `Write` tool does NOT reliably trigger a recompile on an unfocused editor → tests/`execute_code` run a **stale assembly**. (`Write`/`Edit` are fine for non-asset files: this vault, asmdef JSON, etc.) +- **Edit Assets `.cs` ONLY via MCP `apply_text_edits` / `create_script`** (Unity's scripting pipeline) — the raw `Write` tool does NOT reliably trigger a recompile on an unfocused editor → tests/`execute_code` run a **stale assembly**. A raw-`Write`-created NEW `.cs` is worse — it gets **no `.meta` / no test-discovery** until `refresh_unity scope=all mode=force` (it compiles, but EditMode silently won't run it). (`Write`/`Edit` are fine for non-asset files: this vault, asmdef JSON, etc.) For comment/string-precise edits to existing scripts, `script_apply_edits` **`anchor_replace`** (regex anchor) + **`delete_method`** work cleanly even on a `struct : ISystem` (unlike `replace_method`). - **`apply_text_edits` with MULTIPLE non-adjacent edits in one call can MISALIGN** (a paired replace+delete hit the line *above* the target). One edit per call (or strict bottom-first), always with `precondition_sha256` (it returns the current SHA on mismatch). **`create_script` won't overwrite** an existing path; full-file rewrites = whole-span `apply_text_edits` (its brace-balance validator guards botched spans) or `manage_script delete`+`create_script` (NON-GUID-referenced files only — systems/tests, never authoring MonoBehaviours). `script_apply_edits replace_method` is safe for class methods but still **can't target a `struct : ISystem`** (use whole-span). [[DR-017_Persistent_Base_Player_Driven_Pacing]] - **`execute_code` runs as a method body** — no `using` directives (parse as statements); fully-qualify every type. Identify worlds by `world.Name == "ServerWorld"/"ClientWorld"` (flags overlap a shared `Game` bit). - **`manage_gameobject create` / `manage_prefabs modify_contents` `component_properties` SILENTLY DROP enum + Vector3 fields** — set those via a follow-up `manage_components set_property` and VERIFY through `mcpforunity://scene/gameobject/{id}/component/{Type}` (or read the baked component in `execute_code` after Play). `manage_material set_renderer_color` uses a runtime PropertyBlock that does NOT persist into Play — create + assign a material asset instead. @@ -109,9 +115,9 @@ Long-form originals + the milestone each came from: `Docs/Vault/_Meta/CLAUDE_Bui ## Bootstrap & worlds -- `ProjectM.Simulation.GameBootstrap : ClientServerBootstrap` overrides `Initialize`, sets `AutoConnectPort = 7979` (in-editor auto-connect over IPC), calls `CreateDefaultClientServerWorlds()`. Entering Play Mode creates `ServerWorld` (`WorldFlags.GameServer`) + `ClientWorld` (`WorldFlags.GameClient`). -- `Assets/_Project/Subscenes/Gameplay.unity` is wired into `SampleScene` (GameObject `GameplaySubScene`) as a baking target. Replace `SampleScene` with a dedicated bootstrap scene when building for real. -- **Region split:** one server world; the expedition lives at `base + (1000,0,0)`, hidden per-connection via `GhostRelevancy` (see the Netcode gotchas). Expedition *place* = cosmetic ground/pillars in SampleScene at the +1000 offset; gameplay nodes/gates are baked subscene entities. See [[DR-013_M6_Aether_Cycle_Region_Split]]. +- `ProjectM.Simulation.GameBootstrap : ClientServerBootstrap` overrides `Initialize` with `AutoConnectPort = 0` (M4 — listen/connect is explicit via the `ConnectionConfig` singleton + the per-world ConnectionControlSystems, no auto-connect). **Editor default = instant-into-game + MPPM** (creates `ServerWorld` (`WorldFlags.GameServer`) + `ClientWorld` (`WorldFlags.GameClient`)); the `ProjectM/Boot Into Menu (Editor)` EditorPref flips the MAIN editor to the frontend path. **Player builds boot the UITK frontend menu** (`return false` → one menu world, no netcode worlds until a menu choice). See [[DR-019_Frontend_Menu_Settings_Saves_Build]]. +- **Scenes:** `Assets/Scenes/MainMenu.unity` (build index 0) boots the UITK frontend (menu world only, no netcode); `Assets/Scenes/Game.unity` (index 1) holds gameplay with `Assets/_Project/Subscenes/Gameplay.unity` wired in as the baked subscene (GameObject `GameplaySubScene`). `SampleScene` / `DevSandbox` are kept as reference / dev scenes. The on-demand lifecycle (`WorldLauncher` / `SessionRunner` / `MainMenuController`) creates the right worlds per menu choice (Single / Host / Join), THEN `LoadScene(Game)` — the subscene streams into the on-demand world only if a netcode world is the `DefaultGameObjectInjectionWorld` when the scene loads. +- **Region split:** one server world; the expedition lives at `base + (1000,0,0)`, hidden per-connection via `GhostRelevancy` (see the Netcode gotchas). Expedition *place* = cosmetic ground/pillars in the Game scene at the +1000 offset; gameplay nodes/gates are baked subscene entities. See [[DR-013_M6_Aether_Cycle_Region_Split]]. ## DOTS / ECS conventions (authoritative summary) diff --git a/Docs/Vault/00_Home/Home.md b/Docs/Vault/00_Home/Home.md index 08707794c..ea41f9a99 100644 --- a/Docs/Vault/00_Home/Home.md +++ b/Docs/Vault/00_Home/Home.md @@ -2,7 +2,7 @@ tags: - moc - home -updated: 2026-06-04 +updated: 2026-06-06 permalink: gamevault/00-home/home --- @@ -15,8 +15,8 @@ Multiplayer game on **Unity DOTS** (Entities) + **Netcode for Entities** (server - **Vision** → [[Pillars]] — design pillars & locked decisions · [[Identity]] — the fiction (sci-fi frontier colony) - **Game Design** → [[Systems_Index]] — per-system design docs - **Roadmap** → [[Milestones]] · [[Backlog]] -- **Sessions** → `07_Sessions/2026/` — dated work logs (latest: [[2026-06-04_M8_Persistent_Base_Player_Driven_Pacing]]) -- **Decisions** → `07_Sessions/_Decisions/` — decision records DR-001 … DR-017 · [[DR-001_Netcode_Test_Harness]] · latest [[DR-017_Persistent_Base_Player_Driven_Pacing]] +- **Sessions** → `07_Sessions/2026/` — dated work logs (latest: [[2026-06-06_Cleanup_Alignment]]) +- **Decisions** → `07_Sessions/_Decisions/` — decision records DR-001 … DR-021 · [[DR-001_Netcode_Test_Harness]] · latest [[DR-021_HUD_UITK_BuildPalette]] - **Meta** → [[Documentation_Protocol]] · [[Tags]] - **Templates** → [[Session_Log_Template]] · [[Decision_Record_Template]] diff --git a/Docs/Vault/02_Game_Design/Systems_Index.md b/Docs/Vault/02_Game_Design/Systems_Index.md index 399d26372..743067b55 100644 --- a/Docs/Vault/02_Game_Design/Systems_Index.md +++ b/Docs/Vault/02_Game_Design/Systems_Index.md @@ -2,7 +2,7 @@ tags: - design - index -updated: 2026-06-04 +updated: 2026-06-06 permalink: gamevault/02-game-design/systems-index --- @@ -48,6 +48,17 @@ One design doc per gameplay system, linked here. Each should state: purpose, com - **Systems:** `ResourceHarvestSystem` **unified** to sweep nodes AND clutter in one best-target loop (required — two separate sweeps would double-destroy an overlapping projectile at ECB playback; `math.max(1,(int)yield)` guards the immortal-sink); `ExpeditionFieldSystem` scatters/clears clutter beside the node field (distinct seed); `BuildPlaceSystem` **unchanged** (already type-generic) — new generic `StructureAuthoring{byte Kind}` + two additive `StructureCatalog` rows + `BuildSendSystem` V/N keys. Client: `WorldFeedbackSystem` (observe-only `PresentationSystemGroup` — chip/shatter juice, proximity-gated so region-transit stays silent) + live-tunable `WorldFeelConfig`. - **Netcode/world shape:** clutter = ownerless interpolated, region-scoped via GhostRelevancy (like nodes); Wall/Pylon = ownerless interpolated structure ghosts (`PlacedStructure.Type` byte-additive → **no re-bake**); Wall carries a `PhysicsCollider` (CC-blocking), Pylon cosmetic. Visual: `M_Aether_Wild`/`M_Aether_Ordered` palette materials + a classic-URP Blightfield rock basin in `Game.unity` (relief via props, **no terrain**). Status: **built + validated** (EditMode **142/142**; in-editor Play introspected via execute_code; 4-lens adversarial review → 4 findings fixed). Decisions: [[DR-018_World_Space_Cohesion_Pass]] (builds on the M6 region split / build pipeline + M8 persistent base — those systems are documented in their DRs). +### M7 — Automation: self-running production chains (Harvester → Conveyor → Fabricator) · [[2026-06-05_M7_Automation]] + +- **Components** (`ProjectM.Simulation/Automation`): `Harvester{byte ResourceId;int Yield;int PeriodTicks}` (fixed-yield generator), `Fabricator{byte In/OutResourceId;int In/OutAmount;int PeriodTicks}` (recipe → global ledger), `Conveyor{byte Direction;int PeriodTicks}`; server-only buffers `MachineInput`/`MachineOutput` (DISTINCT types, NO `[GhostField]`); `ConveyorItem` (enableable, baked DISABLED); `RuntimePlacedTag` (player-built = persistable, single source of truth). Pure math: `ProductionMath` (NeedsInit/CyclesDue/RemainingTicks/RestoreNextTick), `ConveyorMath` (DirOffset/CellKey/ResolveMoves — deterministic), `MachineSlotMath` (byte-id deposit/withdraw, the non-replicated twin of `StorageMath`). +- **Systems** (`ProjectM.Server/Automation`, server-only, plain `SimulationSystemGroup` ordered chain `[UpdateAfter(PredictedSimulationSystemGroup)]` → Harvester → Conveyor → Fabricator): `HarvesterProductionSystem` (→ own `MachineOutput`), `ConveyorTransportSystem` (pull adjacent upstream output → `ResolveMoves` → settle `ConveyorItem`/deposit sink `MachineInput`), `FabricatorProductionSystem` (input-limited recipe → global ledger). Persistence: `SaveStructureScan` (shared autosave/quit scan of `RuntimePlacedTag` structures), one-shot `BaseRestoreSystem` (charge-free replay), `SaveData` v2 (`StructureSave[]` + flat `StructureIoRow[]`, cooldown as epoch-independent REMAINING ticks). Build: `BuildPlaceSystem` stamps conveyor `Direction`+`RuntimePlacedTag`; `BuildSendSystem` H/F/C keys + `[`/`]` rotate; `BuildPlaceRequest` +`byte Direction`. Authoring: 3 MonoBehaviours + bakers; `Harvester/Fabricator/Conveyor.prefab` (dup `Turret.prefab`) in the catalog. +- **Netcode shape:** production is **SERVER-ONLY** (no prediction/rollback); results replicate ONLY via the global `StorageEntry` ledger ([GhostField], untagged CycleDirector ghost) + `PlacedStructure.Type`; per-machine I/O buffers never hit the wire. "Offline catch-up" = within-session tick math + a preserved stockpile across quit (NO wall-clock minting). Status: **built + runtime-validated** (real netcode worlds, server==client; quit→Continue restore; EditMode **190/190**). Decisions: [[DR-020_M7_Automation_Production_Chains]] (builds on [[DR-014_M6_Build_Structures_Automation_Foundation]]). + +### Presentation — UI Toolkit HUD + build-palette HUD · [[2026-06-05_HUD_Rework_UITK_BuildPalette]] + +- **Client (presentation/UI):** `HudSystem` rewritten on **UI Toolkit** (client `PresentationSystemGroup`, observe-only) — a runtime UIDocument (shared `RuntimePanelSettings`, sortingOrder 50 behind the pause overlay; root `pickingMode=Ignore`) in the Aether-cyan `MenuUi`/`HudUi` palette: health+cooldown bars, HUSKS, phase/cycle/countdown + resources + location + goal, DOWNED overlay, and a **build-palette bar** lazy-built from the client `StructureCatalog` (cost + affordability + selection highlight). `BuildPaletteState` (client-local), `BuildPreviewMath` (pure validity, unit-tested), `BuildSendSystem` (cursor→cell ground **ghost** green/red, left-click place, right-click/Esc cancel, [/]/R rotate; self-contained guards), `PlayerInputGatherSystem` (fire suppressed in build mode), `PauseMenuController.Open` (pause guard). `CombatFeedbackSystem` damage numbers re-skinned to the Aether/Blight palette. +- **Netcode shape:** none new — UI observes replicated state (ledger/cycle/goal/player ghost) + the client-baked catalog; placement reuses the M7 `BuildPlaceRequest` RPC (server-authoritative). Status: **built + runtime-validated** (UITK render + palette + selection + ground ghost + placement; EditMode **194/194**). Decisions: [[DR-021_HUD_UITK_BuildPalette]] (builds on [[DR-019_Frontend_Menu_Settings_Saves_Build]] UITK frontend + [[DR-020_M7_Automation_Production_Chains]] structures). + ## Conventions DOTS/ECS conventions live in repo `CLAUDE.md` and the `dots-dev` skill's `dots-conventions.md`. Don't duplicate volatile API details here — link to context7-derived notes instead. \ No newline at end of file diff --git a/Docs/Vault/06_Roadmap/Backlog.md b/Docs/Vault/06_Roadmap/Backlog.md index 33bebf71a..34eb17c2b 100644 --- a/Docs/Vault/06_Roadmap/Backlog.md +++ b/Docs/Vault/06_Roadmap/Backlog.md @@ -2,7 +2,7 @@ tags: - roadmap - backlog -updated: 2026-06-04 +updated: 2026-06-06 permalink: gamevault/06-roadmap/backlog --- @@ -14,6 +14,31 @@ Unordered pool of candidate work. Promote to a [[Milestones|milestone]] when com > **2026-06-04 World-space cohesion pass** ([[2026-06-04_World_Space_Cohesion_Pass]], [[DR-018_World_Space_Cohesion_Pass]]) — delivered + validated (EditMode 127→**142**): **destructible "clearing"** (`BlightClutter` ghost smashed by the **unified** harvest sweep → Biomass scrap + smash juice, scattered/cleared with the expedition field); **build out the base** (additive `Wall`=5 / `Pylon`=6 buildables — Wall blocks the player, V/N keys); and a first **visual cohesion layer** (Aether cyan/orange material pair, the 3 new ghosts recoloured, the barren +1000 Blightfield turned into a 21-piece Synty rock/debris basin + orange wild-lights — relief via props, **no terrain**). In-editor Play introspected via execute_code; a 4-lens adversarial review (23→4) caught + fixed an immortal-shot-sink (fractional `ScrapPerHit` truncation). Base-side cohesion accents + clearing-as-gating deferred (see the 2026-06-04 follow-ups below). +> **2026-06-05 Game infrastructure** ([[2026-06-05_Game_Infrastructure_Menu_Settings_Saves]], [[DR-019_Frontend_Menu_Settings_Saves_Build]]) — delivered + runtime-validated (EditMode 142→**152**): a **UI Toolkit main menu** (Single/Host/Join+IP/Continue/Settings/Quit) on the **Netcode frontend pattern** (on-demand `Create*World`, subscene streams into them, frame-boundary `DisposeAllWorlds` teardown, MenuWorld recreated on return); **Graphics + Audio settings** that drive the engine (`SettingsService` JSON at `persistentDataPath`; `GameVolume` Master/Music/Sfx buses); a **saves foundation** (versioned single-slot JSON, **born-correct** goal/ledger load at director spawn, autosave on Siege→Calm + quit-to-menu); editor `ProjectM/Build/Windows Player` + Boot-Into-Menu toggle; build order MainMenu(0)/Game(1). Editor instant-play + MPPM loop unchanged by default. + +> **2026-06-05 M7 — Automation** ([[2026-06-05_M7_Automation]], [[DR-020_M7_Automation_Production_Chains]]) — delivered + runtime-validated (EditMode 152→**190**): the full **Harvester → Conveyor → Fabricator** self-running chain (server-only, deterministic), auto-gathering EXISTING resources (default 2 Ore → 1 Aether) into the global ledger; **SaveData v2** structure persistence (quit autosave → Continue restore, charge-free, born-correct, chain resumes); deterministic conveyor (`ConveyorMath.ResolveMoves`, shuffle-invariant); single gated catch-up (`ProductionMath`); one-shot `BaseRestoreSystem`; `RuntimePlacedTag` source-of-truth; 3 machine prefabs duplicated from `Turret.prefab` + catalog rows. + +> **2026-06-06 Cleanup & alignment** ([[2026-06-06_Cleanup_Alignment]]) — repo hygiene before more dev (no gameplay change): deleted crash-recovery scenes (`_Recovery/`) + a stray empty `Assets/Resources.meta`; removed the legacy IMGUI `ConnectionUI` (3 scenes + script + 4 stale comments); DRY'd the duplicated UITK `Round`/`Border` helpers into `MenuUi` (canonical) + small HUD/build-input tidy (husk-count cache, table-driven build hotkeys); re-aligned `CLAUDE.md` (scene split, on-demand frontend, M7 Automation, disk persistence, UITK HUD, new build-gotchas) + the vault roadmap to the three 2026-06-05 sessions. EditMode green, console clean; landed as grouped commits. + +### 2026-06-06 Cleanup follow-ups +- [ ] **Optional further code-tidy** (low value; deliberately skipped this pass to limit churn on freshly-validated systems — none affect behavior): `SettingsService` migrate-then-clamp ordering; `ConveyorTransportSystem`'s 6 parallel snapshot arrays → one struct array; `WorldFeedbackSystem.TintForResource` if-chain → byte→Color lookup; `PlayerInputGatherSystem` device-active dedup. [[2026-06-06_Cleanup_Alignment]]. + +### 2026-06-05 M7 Automation follow-ups +- [ ] **Throughput visuals** (item-on-belt): server-only machine buffers don't reach the client (clients see only `PlacedStructure.Type`); add ONE small replicated byte if live belt visuals are wanted. +- [x] **Build-palette HUD + ghost preview** — done 2026-06-05: UITK build-palette bar (6 buildables + cost + affordability + selection) + click-to-place with a green/red ground ghost, conveyor rotate, fire-suppress. Conveyor-facing arrow/icons still open. [[DR-021_HUD_UITK_BuildPalette]], [[2026-06-05_HUD_Rework_UITK_BuildPalette]]. +- [ ] **Relevancy ceiling** — `RegionRelevancySystem`'s O(structures×connections)/tick scan becomes load-bearing at higher conveyor counts (DR-014 ceiling note); batch it when counts grow. +- [ ] **Recipe depth** — multi-input fabricator recipes; fabricator→conveyor output chaining (additive: give the fabricator a `MachineOutput`); per-machine distinct meshes (machines reuse the Turret mesh as a placeholder). +- [ ] **Operator live play-through** of the chain + a real 2-build LAN run under latency (single-client validated this pass). + +### 2026-06-05 Game-infrastructure follow-ups +- [x] **Build the standalone** — the first player build hit a pre-existing URP blocker (`UniversalRenderPipelineGlobalSettings` `m_AssetVersion=11` > package `k_LastVersion=10`, from the reverted 6.6 alpha; URP migrates forward-only so its build preprocessor rejected it — blocked builds, not editor Play). Fixed (reset the stamp to 10); `ProjectM/Build/Windows Player` now produces a complete Mono Win64 standalone (`Builds/Windows/`, both scenes + baked subscene). See [[2026-06-05_Game_Infrastructure_Menu_Settings_Saves]]. +- [ ] **Operator-run validation**: launch `Builds/Windows/ProjectM.exe` → confirm it boots the menu → Single/Host/Join play; a real **2-build LAN Host↔Join**; MPPM co-op regression in default-editor mode. +- [ ] **Controls settings + key rebinding** (Input System rebinding UI) — deliberately out of the Graphics/Audio scope this pass. +- [ ] **Full base-state saves** (placed structures / threat / storage) — the `SaveData` schema is versioned so this is additive (no migration of the ledger/goal slice). +- [ ] **Unity Relay** for remote (non-LAN) Host/Join — swap the endpoint source feeding `ConnectionConfig` (the frontend lifecycle is transport-agnostic). +- [ ] **Dedicated-server build** — the `RequestedPlayType==Server` auto-host branch is in but UNTESTED this pass. +- [x] **Remove the legacy IMGUI `ConnectionUI`** — done 2026-06-06 (cleanup pass): the `NetConnectionUI` GameObject removed from `Game.unity` / `DevSandbox.unity` / `SampleScene.unity`, `ConnectionUI.cs` deleted, and the 4 stale doc-comments repointed to the UITK `MainMenuController` / `WorldLauncher`. The UITK frontend menu now owns Host/Join+IP everywhere. [[2026-06-06_Cleanup_Alignment]]. +- [ ] **Volume-slider theming**: sliders render via the default runtime theme; if a real build shows them unstyled, swap to +/- cycle rows (the enum settings already use cycle rows). - [x] Upgrade Unity 6.4 → 6.6 — done (now `6000.6.0a6`). Entities/Collections/Graphics → 6.5.0; **Netcode → 6.6.0 and Physics → 6.5.0 also renumbered into the editor line** (not independent 1.x as [[DR-001_Netcode_Test_Harness]] assumed). See [[2026-05-30_M1_Player_Slice]]. - [x] Define the core gameplay loop and the first predicted player ghost — delivered as M1 ([[2026-05-30_M1_Player_Slice]]). - [x] **Re-validate the M1 play-tick on a stable Unity 6.x** — moot/subsumed 2026-06-04: M1–M6 are all runtime-validated on the stable **6.4.7** line (the 6.6 alpha netcode bug never affected 6.4.7). The original "blocked on 6.6 alpha" framing ([[DR-002_Unity66_Alpha_Netcode_Transport]]) no longer applies. @@ -21,7 +46,7 @@ Unordered pool of candidate work. Promote to a [[Milestones|milestone]] when com - [x] Optional template cleanup: remove `com.unity.visualscripting`, `Assets/TutorialInfo/`, `Assets/Readme.asset` — **done 2026-06-03** (pre-M6 cleanup; see the "2026-06-03 Visual & Controls Polish" section below + [[2026-06-03_Pre_M6_Cleanup]]). Duplicate of the now-checked item there. - [x] Decide **relay provider** before M4 — resolved: **Direct IP/LAN now, Unity Relay later** ([[DR-005_M4_Connection_Model_Direct_IP]], [[2026-06-01_M4_LAN_CoOp_And_Classification_Fix]]). - [x] Decide home-base **grid 2D vs 3D** before M6 — resolved 2026-06-02: **planar single-level `int2` grid**, CellSize 1.0, 32×32 plot (full 3D/stacked deferred). Locked in `BaseGridMath` — [[DR-008_M5_HomeBase_BaseLayer_Storage]]. -- [ ] Decide **production replication** (predicted vs server-only) before M7 (automation). +- [x] Decide **production replication** (predicted vs server-only) before M7 (automation) — resolved 2026-06-05: **server-only** (production runs in the plain server `SimulationSystemGroup` `[UpdateAfter(PredictedSimulationSystemGroup)]`; results replicate via the existing global ledger + `PlacedStructure` ghosts, never predicted; per-machine I/O buffers stay server-only). Delivered as **M7** ([[DR-020_M7_Automation_Production_Chains]], [[2026-06-05_M7_Automation]]). - [x] **M2 follow-up — Burst cache health** — confirmed clean 2026-06-03: warm focused play shows **no** "not a known Burst entry point" managed-fallback messages + fast (~3s) play-enter. The M2-era corruption is gone. [[2026-06-03_Pre_M6_Cleanup]] (orig [[2026-05-31_M2_Combat]] / [[DR-003_M2_Combat_Netcode_Architecture]]). - [ ] **M2 follow-up — live interactive fire test** (focused Play Mode: press Space / LMB / RT → predicted projectile + dummy HP drop). The server combat loop + replication are validated; the input→`AbilityFireSystem`→predicted-spawn→classification path is only validated structurally. - [x] **M2 follow-up — mouse-cursor aim for KBM** — done 2026-06-03: client-side camera ground-ray cursor aim rides the existing `PlayerInput.Aim` `[GhostField]` (no new netcode surface); device auto-switch (KBM/gamepad) replicated as a `byte`; strafe-while-aiming free. [[DR-012_Aim_Controls_Cursor_Gamepad]], [[2026-06-03_Aim_Controls_Cursor_Gamepad]]. @@ -81,6 +106,6 @@ Surfaced by [[DR-018_World_Space_Cohesion_Pass]] (the balanced-slice scope delib - [ ] **Base-side cohesion accents**: cyan ordered-Aether lights + perimeter relief + re-enabled tiled base ground (deferred this pass to avoid clashing with the tuned SSAO/ACES look — the Blightfield got the dressing budget). - [ ] **Clearing as a spatial mechanic**: shipped as juice + scrap only; make cleared cells buildable / reveal hidden nodes (the "functional gating" fork from the intake gate). -- [ ] **Build-palette HUD + ghost-preview**: dedicated V/N keys for now; a selectable palette + placement preview is the UX follow-up. +- [x] **Build-palette HUD + ghost-preview** — done 2026-06-05 (UITK palette + click-to-place ground ghost). [[DR-021_HUD_UITK_BuildPalette]]. - [ ] **Per-`Variant` clutter meshes**: `BlightClutter.Variant` is replicated + round-robined but the prefab is single-mesh; swap per-variant meshes (additive, no schema change). - [x] **Focused-editor validation** (2026-06-04): confirmed in Play — Wall **stops the player CC at the collider face** (X 2.50→3.257, server==client); Blightfield **reads orange-wild rock basin** vs cyan base (real player-view shots); **clutter-clear loop** deposits Biomass + shatters + replicates (BIO 0→10, clutter 14→13); node harvest works (AETHER 0→30). Console clean. **Still open:** deep basin density/lighting **feel-tuning**. \ No newline at end of file diff --git a/Docs/Vault/06_Roadmap/Milestones.md b/Docs/Vault/06_Roadmap/Milestones.md index 4362b2dfa..73552a32c 100644 --- a/Docs/Vault/06_Roadmap/Milestones.md +++ b/Docs/Vault/06_Roadmap/Milestones.md @@ -2,7 +2,7 @@ tags: - roadmap - milestones -updated: 2026-06-04 +updated: 2026-06-06 permalink: gamevault/06-roadmap/milestones --- @@ -19,9 +19,12 @@ permalink: gamevault/06-roadmap/milestones | **M5.5 — Game feel & identity** | Bridge "tech-demo → game": the **Husk** enemy (server AI, interpolated ghost), player death/respawn, combat juice (damage numbers/VFX/SFX/camera shake), a core HUD, and a sci-fi look pass — under the new fiction ([[Identity]], sci-fi frontier colony) | ✅ Done 2026-06-02 — runtime-validated on 6.4.7: Husks spawn(6)+replicate+chase+strike; death→respawn loop; HUD (health/cooldown/threat/downed); emissive dark-sci-fi look. EditMode **74/74**. ctx7-verified APIs. **Deepened same day:** auto-target on Husks, replicated respawn-invulnerability, and a `WaveSystem` threat director (escalating waves of 3 Husk variants — Grunt/Swarmer/Brute) replacing the flat sustain — runtime-validated (wave 1→2 escalation 4→6, distinct maxHP 30/15/80). [[DR-009_GameFeel_Identity_FirstBlood]], [[2026-06-02_GameFeel_Identity]], [[2026-06-02_GameFeel_Deepening]] | | **— 2026-06-03 Visual & controls polish —** | Non-milestone polish layered on M5.5 (no mechanical rework): HDRP→URP art import + reusable converter; a cohesive **Synty** sci-fi colony world (cosmetic SampleScene GameObjects) + **GabrielAguiar** combat VFX; **KBM mouse-cursor aim + gamepad aim** with last-actuation device auto-switch (rides the existing `PlayerInput.Aim` ghost field). | ✅ Done 2026-06-03 — [[DR-010_Art_Import_URP_Conversion_Visual_Upgrade]], [[DR-011_Synty_World_VFX_Integration]], [[DR-012_Aim_Controls_Cursor_Gamepad]] | | **— 2026-06-03 Pre-M6 cleanup —** | Loose-ends pass before M6: vault roadmap reconcile, Unity-template + orphaned-material removal, rate-limited turning, console/runtime health gate. | ✅ Done 2026-06-03 — [[2026-06-03_Pre_M6_Cleanup]] | -| **M6 — The Aether Cycle (core loop)** | Reframed from "grid build placement" into the first vertical slice of the **core game loop**: Expedition (gather) → Defend (wave) → Build/Charge (spend), persistent base + procedural sorties, escalating toward a goal. Build placement is now Stage 3 of this milestone. | 🚧 In progress 2026-06-03 — **Stages 0–4 done + runtime-validated** on 6.4.7 (M6 core loop systems complete): **base/expedition split via coordinate-region + `GhostRelevancy`** (player transit despawns/re-grants the other region's ghosts; server==client); a **server phase-director** (Expedition→Defend→Build→Expedition auto-cycle, cycle 1→2, Husk `WaveSystem` only in Defend, escalation 4→6); and **resources + harvest** — a **global CycleDirector ghost** carrying the replicated `CycleState` + a shared resource **ledger** (relevant in every region, unlike the base storage), a procedural **expedition field** (8 resource-node ghosts seeded per cycle, region-scoped), and a tunnel-safe **harvest** sweep depositing into the ledger; client **HUD** shows phase + resource counts. Supersedes DR-008's "split requires streaming" framing. **Stage 3** (generic automation-ready **structure model** + data-driven catalog + grid **build-placement** RPC with co-op-atomic commit + a hitscan **turret** that auto-defends + **ability tiers** via a bounded StatModifier) and **Stage 4 goal meter** are **done + validated** (turret placed/Ore-deducted/replicated; two same-tick requests → one build; turrets killed the wave; ability damage 20→30 bounded; goal increments per cycle). Disk-persistence **writer deferred to post-M7** (M7-additive surface — tick fields + frozen schema — baked now); the structure model is the M7 production-chain foundation. Playable walk-in-gate loop with build/spend, visible in the HUD. — [[DR-014_M6_Build_Structures_Automation_Foundation]] — [[DR-013_M6_Aether_Cycle_Region_Split]], [[2026-06-03_M6_Aether_Cycle_CoreLoop]] | -| **— 2026-06-04 Polish & backlog-clear pass —** | Comprehensive *sequential* polish across hygiene/reconcile, system-level tests, HUD (TMP + replicated wave number), procedural audio + combat juice, ghost-prop reskin + post-processing, new gameplay content, controls/UX, and a validation-harness + operator handoff. Clears the open [[Backlog]]. | 🚧 In progress 2026-06-04 | +| **M6 — The Aether Cycle (core loop)** | Reframed from "grid build placement" into the first vertical slice of the **core game loop**: Expedition (gather) → Defend (wave) → Build/Charge (spend), persistent base + procedural sorties, escalating toward a goal. Build placement is now Stage 3 of this milestone. | ✅ Done 2026-06-03 (forced-timer rhythm later superseded by M8's player-driven pacing) — **Stages 0–4 done + runtime-validated** on 6.4.7 (M6 core loop systems complete): **base/expedition split via coordinate-region + `GhostRelevancy`** (player transit despawns/re-grants the other region's ghosts; server==client); a **server phase-director** (Expedition→Defend→Build→Expedition auto-cycle, cycle 1→2, Husk `WaveSystem` only in Defend, escalation 4→6); and **resources + harvest** — a **global CycleDirector ghost** carrying the replicated `CycleState` + a shared resource **ledger** (relevant in every region, unlike the base storage), a procedural **expedition field** (8 resource-node ghosts seeded per cycle, region-scoped), and a tunnel-safe **harvest** sweep depositing into the ledger; client **HUD** shows phase + resource counts. Supersedes DR-008's "split requires streaming" framing. **Stage 3** (generic automation-ready **structure model** + data-driven catalog + grid **build-placement** RPC with co-op-atomic commit + a hitscan **turret** that auto-defends + **ability tiers** via a bounded StatModifier) and **Stage 4 goal meter** are **done + validated** (turret placed/Ore-deducted/replicated; two same-tick requests → one build; turrets killed the wave; ability damage 20→30 bounded; goal increments per cycle). Disk-persistence **writer deferred to post-M7** (M7-additive surface — tick fields + frozen schema — baked now); the structure model is the M7 production-chain foundation. Playable walk-in-gate loop with build/spend, visible in the HUD. — [[DR-014_M6_Build_Structures_Automation_Foundation]] — [[DR-013_M6_Aether_Cycle_Region_Split]], [[2026-06-03_M6_Aether_Cycle_CoreLoop]] | +| **— 2026-06-04 Polish & backlog-clear pass —** | Comprehensive *sequential* polish across hygiene/reconcile, system-level tests, HUD (TMP + replicated wave number), procedural audio + combat juice, ghost-prop reskin + post-processing, new gameplay content, controls/UX, and a validation-harness + operator handoff. Clears the open [[Backlog]]. | ✅ Done 2026-06-04 — [[2026-06-04_Polish_Backlog_Pass]] | | **M8 — Persistent base & player-driven pacing** | Replace the forced-timer cycle with a persistent **Calm** base (no countdown) + **player-initiated** expeditions + **event-triggered** sieges (composite **ThreatDirector**, post-expedition retaliation); **dev-tools-over-RPC** (spawn/force-siege/grant/teleport/god-mode) + a dedicated **Game** scene + **DevSandbox**. | ✅ Done 2026-06-04 — runtime-validated on 6.4.7 (server+client, focused editor): boots into **Calm** (no timer); a returning player arms a retaliation siege (Gate→ThreatDirector→CyclePhase→Wave), the atomic WaveState seed spawns the **exact** configured size, **server==client** (phase + husk ghosts replicated); dev overlay drives the real server RPCs (unconditional wire type → handshake intact); god-mode baked present+disabled; `Game.unity` boots the calm base with the new "AT BASE — deploy when ready" HUD. EditMode **137/137**. Supersedes the M6 forced-timer rhythm. — [[DR-017_Persistent_Base_Player_Driven_Pacing]], [[2026-06-04_M8_Persistent_Base_Player_Driven_Pacing]] | -| **M7 — Automation** | Self-running tick-based production chains (deterministic offline catch-up) | ⬜ | +| **— 2026-06-05 Game infrastructure —** | The surrounding shell to build + test the game: a **UI Toolkit main menu** (Single / Host / Join+IP / Continue / Settings / Quit) on the **Netcode frontend pattern** (on-demand world creation, subscene-streams-into-them, clean teardown); **Graphics + Audio settings** that drive the engine (persisted JSON); a **saves foundation** + minimal slice (born-correct goal/ledger load, autosave on Siege→Calm + quit); a one-click **build** menu + scene order. | ✅ Done 2026-06-05 — runtime-validated (focused editor): menu renders → Single → worlds created → subscene streams → connect→spawn→playable → teardown→menu → Continue **born-correct** (charge/ledger restored, no flicker); settings apply live; EditMode **152/152**. Editor instant-play + MPPM loop unchanged (toggle to test the menu); builds boot the menu. — [[DR-019_Frontend_Menu_Settings_Saves_Build]], [[2026-06-05_Game_Infrastructure_Menu_Settings_Saves]] | +| **M7 — Automation** | Self-running tick-based production chains (deterministic offline catch-up) | ✅ Done 2026-06-05 — **full Harvester → Conveyor → Fabricator chain, server-only + deterministic, auto-gathering EXISTING resources (default 2 Ore → 1 Aether) into the global ledger so setup compounds; persistence folded in (SaveData v2).** Runtime-validated on 6.4.7 (real netcode worlds): catalog bakes the 3 machine ghost prefabs; a placed live chain runs end-to-end (Ore cost withdrawn; Harvester→Conveyor→Fabricator→ledger), **server Aether == client Aether** (replicated); quit autosaved v2 (3 structures + epoch-independent REMAINING-tick cooldowns), Continue restored all 3 charge-free at the exact cells with born-correct ledger and the chain **resumed producing**. Deterministic conveyor (snapshot→stable-sort→at-most-one-claim→stall, shuffle-invariant); single gated catch-up path (`CyclesDue` lower-bound 0, input-limited, period-0-guarded); one-shot `BaseRestoreSystem`; `RuntimePlacedTag` single-source-of-truth. EditMode **190/190**. Resolves "production replication = **server-only**". — [[DR-020_M7_Automation_Production_Chains]], [[2026-06-05_M7_Automation]] | +| **— 2026-06-05 HUD rework (UITK + build palette) —** | Convert the in-game HUD to UI Toolkit (consistent with the menu / pause / settings) + a full build-palette HUD: click-to-place with a green/red ground-ghost, conveyor rotate, fire-suppress; combat juice re-skinned to the Aether palette. | ✅ Done 2026-06-05 — runtime-validated (real netcode session): UITK HUD renders all panels; palette shows 6 buildables + costs; selection highlights + a live ground ghost; placement via the build RPC works. EditMode **190→194**. — [[DR-021_HUD_UITK_BuildPalette]], [[2026-06-05_HUD_Rework_UITK_BuildPalette]] | +| **— 2026-06-06 Cleanup & alignment —** | Repo hygiene before more dev: delete dead assets (`_Recovery/`, stray `Resources.meta`), remove the legacy IMGUI `ConnectionUI` (scenes + script + stale comments), DRY the UITK `Round`/`Border` helpers + small HUD/build refactors, and align `CLAUDE.md` + the vault to the scene split / Automation / Saves / UITK surfaces. | ✅ Done 2026-06-06 — EditMode green, console clean; grouped commits. — [[2026-06-06_Cleanup_Alignment]] | Promote items from [[Backlog]] here when committed. \ No newline at end of file diff --git a/Docs/Vault/07_Sessions/2026/2026-06-05_Game_Infrastructure_Menu_Settings_Saves.md b/Docs/Vault/07_Sessions/2026/2026-06-05_Game_Infrastructure_Menu_Settings_Saves.md new file mode 100644 index 000000000..60c1fcfca --- /dev/null +++ b/Docs/Vault/07_Sessions/2026/2026-06-05_Game_Infrastructure_Menu_Settings_Saves.md @@ -0,0 +1,82 @@ +--- +date: 2026-06-05 +type: session +tags: [session, frontend, menu, ui-toolkit, settings, saves, netcode, world-lifecycle, build, infra] +--- + +# Session 2026-06-05 — Game infrastructure: Main Menu, Settings, Saves foundation, Build wiring + +## Goal + +Operator: *"Create all the surrounding infra around the game. A main menu with the various options to play (host/join/single etc.), settings that actually adjust settings within the game, persistent saves. Basically everything needed to be able to build the game and test it."* + +Intake decisions: **Saves** = foundation + a minimal real slice (resource ledger + goal charge + settings); **UI** = **UI Toolkit**; **Settings** = **Graphics + Audio** only; **Build** = wire-up only (operator runs the actual standalone build). Full rationale + the de-risked architecture: [[DR-019_Frontend_Menu_Settings_Saves_Build]]. + +The core change: adopt the Netcode **frontend pattern** — boot a menu with NO netcode worlds, create the right worlds **on demand** per menu choice, load the gameplay scene *after* the worlds exist (the subscene-streaming invariant). Replaces the always-create-both-worlds bootstrap. + +## Process + +- **Phase 1–3 (research, context7-first):** 3 Explore agents (netcode world-lifecycle · UITK runtime · settings/persistence/save-slice grounding) + direct ctx7 on the Netcode 1.13.2 `ClientServerBootstrap` frontend API. Confirmed `CreateClientWorld/CreateServerWorld(name)` are the public on-demand helpers ("frontends that allow Create Game vs Join Game"), `RequestedPlayType` is the MPPM switch, `CreateLocalWorld` is **internal** in 1.13.2. +- **Phase 4 (design) + adversarial review (Workflow, ultracode):** one Plan agent drafted the file-by-file map; a 3-critic + synthesis Workflow (netcode-lifecycle · determinism/teardown · UITK/build) red-teamed it. The review changed the design materially (see Decisions). +- Plan approved → 5 staged steps, each `create/edit → read_console → EditMode → (Play where relevant)`. + +## Done + +### Stage 1 — Settings (Graphics + Audio that actually change the game) +- `Client/Settings/GameVolume.cs` (static **Music/Sfx** bus trims — NOT named `AudioSettings`, which collides with `UnityEngine.AudioSettings`; Subsystem-reset), `GameSettings.cs` (`[Serializable]` model + `Clamped()`/`Defaults()`), `SettingsService.cs` (boot-load+apply via `[RuntimeInitializeOnLoadMethod]`; atomic JSON at `persistentDataPath`; `Apply` drives `Screen`/`QualitySettings`/`Application.targetFrameRate` + `AudioListener.volume`=Master + the buses). +- Wired `GameVolume` into the 3 client audio systems (`AmbientAudioSystem`=Music; `CombatFeedbackSystem`+`WorldFeedbackSystem`=Sfx, one edit each at their `PlayClip` helper). **Master rides `AudioListener.volume`; buses multiply per-call — no double-attenuation.** +- Tests: `GameSettingsTests` ×5 (JSON round-trip, clamps, defaults, bus statics). + +### Stage 3 — Saves foundation + minimal real slice +- `Simulation/Persistence/`: `SaveData`(versioned `{GoalCharge,GoalTarget,LedgerRow[],SavedAtMs}` — array as a FIELD), `SaveComponents` (`PendingSave`+`PendingSaveLedgerRow` unmanaged bridge, `SaveRequest` byte flag), `SaveApply.WriteLedger` (Burst-safe buffer copy), `SaveService` (single-slot JSON, atomic, null on bad version). +- **Born-correct load** (review fix): `CycleDirectorSpawnSystem` applies a staged `PendingSave` AT SPAWN (GoalProgress + StorageEntry ledger), so the director ghost never replicates a default first. **Autosave:** `CyclePhaseSystem` raises the `SaveRequest` byte on the Siege→Calm checkpoint; server `SaveWriteSystem` (managed, host-only) writes the JSON. Quit-to-menu also saves (`WorldLauncher.TrySaveFromServer`). +- `ServerConnectionControlSystem` now binds `LoopbackIpv4` when Address=="127.0.0.1" (single-player, no firewall prompt) else AnyIpv4. +- Tests: `SavePersistenceTests` ×5 (schema round-trip, version handling, `WriteLedger` overwrite/empty). + +### Stage 2 — UITK front end + world lifecycle +- `Client/UI/`: `MenuUi` (palette + styled element factories + **EventSystem(+InputSystemUIInputModule)** ensure + Resources PanelSettings load), `SessionRunner` (persistent `DontDestroyOnLoad` coroutine host), `WorldLauncher` (the **single** lifecycle funnel: `StartSession`/`TeardownToMenu`), `SettingsScreen` (shared Graphics/Audio panel — cycle rows + volume sliders), `MainMenuController` (Single/Host/Join+IP/Continue/Settings/Quit; ensures the menu world on return), `PauseMenuController` (Esc overlay) + `PauseMenuSystem` (client spawns it). +- Asset: `Assets/_Project/Resources/RuntimePanelSettings.asset` + `RuntimeTheme.tss` (default runtime theme). Scene: `Assets/Scenes/MainMenu.unity` (Camera, Light, EventSystem, MenuRoot=UIDocument+MainMenuController). + +### Stage 4 — Bootstrap branch + build +- `GameBootstrap.Initialize` branch: **editor default = today's instant-play + MPPM unchanged**; a `ProjectM/Boot Into Menu (Editor)` EditorPrefs toggle (gated to `RequestedPlayType==ClientAndServer` so MPPM virtual players never boot to the menu) flips the main editor to the frontend; **player build** → frontend menu (`return false` → single default menu world), `Server` playtype → auto-host. +- `Editor/BuildTool.cs`: `ProjectM/Build/Windows Player` (`BuildPipeline.BuildPlayer`, MainMenu+Game) + the Boot-Into-Menu toggle. `EditorBuildSettings` reordered: **0:MainMenu 1:Game 2:DevSandbox 3:SampleScene**. + +## Validation + +- **EditMode 152/152** (142 prior + 10 new), console clean. +- **Runtime (focused editor, Play, via `execute_code`/screenshots):** + - Menu **renders** (UITK + hand-made theme + EventSystem) — `MainMenu_Boot.png`. + - **Single Player**: menu world disposed → ServerWorld+ClientWorld created → Game.unity loaded → **subscene streamed into the on-demand worlds** → loopback connect (`serverConns=1`) → player spawned (`serverPlayers=1`) → replicated (`clientPlayerGhosts=1`) → playable base + HUD — `MainMenu_Single_InGame.png`. *(The review's biggest risk — subscene into on-demand worlds — works.)* + - **Teardown**: `ServerWorld`/`ClientWorld` disposed, `MenuWorld` recreated, scene→MainMenu, **quit-save written** → menu shows **Continue** — `MainMenu_WithContinue.png`. + - **Born-correct load** (Continue with a staged save Charge=42, ledger {1:15}): director ghost born `charge=42 target=10 ledger=[(1:15)] pendingRemaining=0` — no default-then-overwrite flicker. + - **Settings apply**: `vSync`, `AudioListener.volume`(master), `GameVolume.Music/Sfx`, `targetFrameRate` all driven by `SettingsService.Apply`. + - Only console output = the **pre-existing** in-editor "Server Tick Batching" perf warning (single-process multi-world cost; not from this work — see [[Backlog]]). + +## Decisions + +- **Created [[DR-019_Frontend_Menu_Settings_Saves_Build]]** (frontend world-lifecycle, UITK menu, settings, saves foundation, build wiring). Extends [[DR-005_M4_Connection_Model_Direct_IP]] (reuses `ConnectionConfig` + control systems + GoInGame handshake unchanged). + +## Build gotchas (→ [[CLAUDE]] addendum candidates) + +- **`CreateLocalWorld` is `internal` in Netcode 1.13.2** — use the public `CreateClientWorld`/`CreateServerWorld` (they register the `ServerWorld`/`ClientWorld` statics that `ConnectionUI`/`DebugOverlay` read); for the menu world, `DefaultWorldInitialization.Initialize(name,false)` (or `return false` from `Initialize`). +- **Subscene streams into on-demand netcode worlds IF a netcode world is the `DefaultGameObjectInjectionWorld` when the scene loads** — dispose the menu world first, set the default to the server world, then `LoadScene(Game)`. Validated (player spawns, replicates). +- **Runtime UITK needs a PanelSettings asset WITH a `themeStyleSheet`** (a `.tss` importing `unity-theme://default`) or it renders nothing; and an **EventSystem + `InputSystemUIInputModule`** (Input System project) or buttons are silently dead. +- **Don't dispose worlds inside an ECS system** — `WorldLauncher` runs all create/dispose/scene-load on a frame-boundary coroutine (`SessionRunner`, `DontDestroyOnLoad`). +- **`Cursor` is ambiguous** under `using UnityEngine.UIElements;` — fully-qualify `UnityEngine.Cursor`. +- **Validate UITK/world-lifecycle by rendering the menu in Play then driving `WorldLauncher` via `execute_code`** — no button-click simulation needed; `runInBackground=True` lets an in-Play editor tick. +- **A reverted URP/engine upgrade can stamp `UniversalRenderPipelineGlobalSettings.asset` with `m_AssetVersion` AHEAD of the current package's `k_LastVersion`** (here 11 vs 10, from the reverted 6.6 alpha — [[DR-002_Unity66_Alpha_Netcode_Transport]]). URP migrates **forward only**, so its build preprocessor (`URPPreprocessBuild`) rejects the "from-the-future" asset with *"is not at last version"* — **blocks builds but NOT editor Play** (so it lurks until the first build). Fix: reflection-set `m_AssetVersion` back to `k_LastVersion`, `SetDirty`+`SaveAssets` (the data already renders under the current package, so only the stamp is wrong). The Mono standalone build then succeeds (~7.5 min; `level0`=MainMenu, `level1`=Game, subscene in `StreamingAssets/EntityScenes`). + +## Build (now succeeds) + +- The first-ever player build surfaced a **pre-existing URP config blocker** (global-settings asset version 11 > package `k_LastVersion` 10, from the reverted 6.6 alpha) — fixed (see Build gotchas). **`ProjectM/Build/Windows Player` now produces a complete Mono Win64 standalone** (`Builds/Windows/ProjectM.exe` + 270 MB `_Data`, both scenes + the baked subscene). The build compiling proves all new runtime code (menu/settings/saves/world-lifecycle/bootstrap) is player-target clean (no editor-only-API leaks). + +## Open / deferred + +- **Operator-run**: launch `Builds/Windows/ProjectM.exe` and confirm it boots the menu → Single/Host/Join play; a real 2-build LAN Host↔Join; MPPM co-op regression in default-editor mode. +- Controls settings + key rebinding (out of scope); full base-state saves (structures/threat/storage — schema is versioned, additive later); Unity Relay; dedicated-server build (branch added defensively, untested). +- Legacy IMGUI `ConnectionUI` left in `Game.unity` (shows a harmless "Net: Connected" label; auto-hides). Optional cleanup — left to avoid touching the load-bearing subscene scene. +- Volume **sliders** render via the default theme; if a build shows them unstyled, swap to +/- cycle rows (the enum settings already use cycle rows). + +## Next + +- Operator: run the build + a real LAN join; then resume **M7 Automation** (the next milestone) or base/automation expansion on the now-buildable, persistent-save foundation. diff --git a/Docs/Vault/07_Sessions/2026/2026-06-05_HUD_Rework_UITK_BuildPalette.md b/Docs/Vault/07_Sessions/2026/2026-06-05_HUD_Rework_UITK_BuildPalette.md new file mode 100644 index 000000000..e9900c159 --- /dev/null +++ b/Docs/Vault/07_Sessions/2026/2026-06-05_HUD_Rework_UITK_BuildPalette.md @@ -0,0 +1,41 @@ +--- +date: 2026-06-05 +type: session +tags: [session, hud, ui-toolkit, build-palette, presentation, uitk, juice] +--- + +# Session 2026-06-05 — HUD rework to UI Toolkit + build-palette HUD (click-to-place + ghost preview) + +## Goal + +Operator: *"Lets do a build pallete hud - rework all the hud in the game use the new uitoolkit so it is consistent."* + +Intake decisions (AskUserQuestion): build palette = **full click-to-place + ground ghost preview** (green/red validity, conveyor rotation, fire suppressed in build mode); scope = the persistent screen HUD → UITK **plus** re-skin the floating combat juice to the Aether palette. Defaults taken: world-space juice (reticle/VFX) + dev overlays (DebugOverlay/legacy IMGUI ConnectionUI) untouched; HUD root `pickingMode=Ignore` so it never eats game clicks; build mode suppresses fire + is suspended while paused. + +## Done + +Converted the in-game HUD from code-built **uGUI** (`HudSystem`'s RawImage bars + legacy `Text`) to **UI Toolkit**, reading in the same Aether-cyan language as the menu/pause/settings (`MenuUi`). New + changed: + +- **`HudUi`** (new) — UITK HUD factories (percent-width bar track+fill, bold labels, group/round/border), all `pickingMode=Ignore` by default. +- **`HudSystem`** (rewrite) — a `PresentationSystemGroup` observe-only system that now owns a runtime **UIDocument** (shared `RuntimePanelSettings`, `sortingOrder=50` — behind the pause overlay's 100), builds the tree on the first frame its root exists (panel-init timing), and pushes ECS values each frame: health+cooldown bars (bottom-left), HUSKS (top-right), phase/cycle/countdown + resources + location + goal bar (top-center), DOWNED overlay, and the **build-palette bar** (bottom-center). The palette is lazy-built from the **client-side `StructureCatalog`** (the subscene streams into both worlds), shows each buildable's Ore cost, greys the unaffordable, highlights the selected (cyan border), and hides off-base. +- **Build palette → click-to-place + ghost** (`BuildSendSystem` extended, `BuildPaletteState` new, `BuildPreviewMath` new): palette buttons set `BuildPaletteState.Selected`; `BuildSendSystem` then raycasts cursor→ground→cell (`AimMath.TryGroundHit`+`BaseGridMath.WorldToCell`), drives a procedural **translucent ground-ghost cube** (green=valid / red=blocked via `BuildPreviewMath.Evaluate` — in-plot, unoccupied, affordable; the client mirror of the server check), **left-click places** (the existing `BuildPlaceRequest` RPC), **right-click/Esc cancels**, **`[`/`]`/R rotates** a conveyor. Self-contained interaction guards: skip placing on the frame the selection changes (`_lastSelected`), suspend while `PauseMenuController.Open`, and `AimPresentation.ForceCursorVisible` shows the cursor in build mode. +- **Fire suppression** (`PlayerInputGatherSystem`, 1 line) — `Fire` is gated by `!BuildPaletteState.Active`, so the place-click never also fires. +- **Combat juice restyle** (`CombatFeedbackSystem`) — floating damage numbers re-skinned to the Aether palette + bold: **Blight orange** when you're hurt, **Aether cyan** when you hit a Husk. +- **`PauseMenuController`** — exposes `static bool Open` (for the build-mode pause guard). +- Keyboard hotkeys (B/V/N/H/F/C, `[`/`]`) kept as a power-user fallback (suppressed in palette mode); the editor `Place*` statics kept for headless validation. + +## Validation + +- **EditMode 190→194** (4 new `BuildPreviewMath` tests: valid / out-of-plot-first / occupied / unaffordable-with-exact-funds-ok). No regressions. +- **Runtime (real netcode session, focused editor, `execute_code` + screenshots):** the UITK HUD renders all panels in the Aether palette (`~HUD` UIDocument, 5 root groups); the build palette shows all 6 buildables + costs from the catalog; selecting Harvester **highlights** its button (cyan border) and activates build mode → `ForceCursorVisible` + a live **ground ghost at the cursor cell**; clearing hides the ghost; placement through the build RPC path works (harvester landed at the target cell). Console clean. Screenshots: `Assets/Screenshots/HudRework_Base.png`, `HudRework_BuildMode.png`. + +## Decisions + +[[DR-021_HUD_UITK_BuildPalette]]. Resolves the M7 follow-up "Build-palette HUD + ghost preview". + +## Next-session intent + +- **Per-buildable icons** + a conveyor-facing arrow on the ghost (text-only palette + cube ghost today). +- **Throughput visuals** (item-on-belt) still need a small replicated field (server-only machine buffers don't reach the client). +- Remove the legacy IMGUI `ConnectionUI` "Net: Connected" label (backlog) — the only non-UITK on-screen UI left in-game. +- Optional: a one-line "BUILD: [name] • click to place, [/] rotate, Esc cancel" hint while in build mode; a build toggle key to open/close the palette focus. diff --git a/Docs/Vault/07_Sessions/2026/2026-06-05_M7_Automation.md b/Docs/Vault/07_Sessions/2026/2026-06-05_M7_Automation.md new file mode 100644 index 000000000..fa481e9cb --- /dev/null +++ b/Docs/Vault/07_Sessions/2026/2026-06-05_M7_Automation.md @@ -0,0 +1,49 @@ +--- +date: 2026-06-05 +type: session +tags: [session, m7, automation, production-chains, conveyor, persistence, netcode, determinism] +--- + +# Session 2026-06-05 — M7 Automation: self-running production chains (Harvester → Conveyor → Fabricator) + +## Goal + +Operator: *"Let us start the automation milestone."* → **M7 — Automation**: self-running, tick-based production chains with deterministic offline catch-up, building on the DR-014 locked structure model. + +Intake decisions (AskUserQuestion): +- **Scope:** the FULL chain (Harvester → Conveyor → Fabricator), not a reduced harvester-only slice. +- **What it makes:** AUTO-GATHER EXISTING resources (no new resource ids) — the chain refines existing resources (default 2 Ore → 1 Aether) into the global ledger so setup compounds. +- **Persistence:** FOLDED IN now (SaveData → v2; player-built structures survive quit→Continue). +- (Informed re-confirm after the adversarial review:) **keep the full chain** with a deterministic conveyor; **"offline catch-up" = within-session tick math + preserved stockpile** across quit (NO wall-clock minting — honors the determinism pillar). + +## Process (ultracode: workflows + adversarial verification) + +- **Phases 1–4 (research + design):** code-graph scan + context7 API verification (NetworkTick/NetworkTime accessors, ECB singleton, server-only `IBufferElementData` stays off the wire); a 3-lens design swarm — code architect · persistence/authoring architect · **adversarial netcode/determinism/reuse critic**. The critic earned its keep: flagged the conveyor as the dominant complexity/risk, a catch-up `period=0` div-by-zero + per-tick/catch-up **double-count** footgun, the restore **tick-rebase** blocker, and the `StorageEntry` singleton-collision; all folded into the plan. +- **Phase 5:** plan approved; the two informed forks resolved (keep full chain; within-session catch-up + preserved stockpile). +- **Phase 6 (source workflow → controlled MCP apply):** a draft→critique Workflow produced the source bundle. **Incident:** the Workflow's general-purpose draft agents ignored the "return source only" instruction and raw-`Write`-wrote the 3 systems + 5 tests into `Assets/` — which **compile but get no `.meta`/test-discovery until `refresh_unity scope=all mode=force`** (logged as native memory [[raw-written-cs-needs-full-refresh]]). Recovered: kept the (high-quality, internally-consistent) agent systems + tests, authored the foundation to satisfy them, and applied everything via MCP with reconciliations — the key one: **`CyclesDue` lower-bound 0, not 1** (prevents a premature mint on a restored `remaining==0` machine), and `BuildPlaceSystem` stamps `LastProcessedTick=0` so runtime-placed machines hit `NeedsInit`. + +## Done — code (compiles clean; EditMode 187→**190**) + +- **Components** (`Simulation/Automation/AutomationComponents.cs`): `Harvester{byte ResourceId;int Yield;int PeriodTicks}`, `Fabricator{byte In/OutResourceId;int In/OutAmount;int PeriodTicks}`, `Conveyor{byte Direction;int PeriodTicks}`, server-only buffers `MachineInput`/`MachineOutput`, `ConveyorItem` (enableable, baked DISABLED), `RuntimePlacedTag`. The DR-014 "recipe column on `StructureCatalogEntry`" was **deliberately dropped** — dead weight; the recipe is baked on each machine component (server-only, not needed client-side). +- **Pure math** (unit-tested, byte-only): `ProductionMath` (`NeedsInit`/`CyclesDue`/`RemainingTicks`/`RestoreNextTick` — the single gated catch-up path), `ConveyorMath` (`DirOffset`/`CellKey`/**`ResolveMoves`** — deterministic + order-independent: snapshot → stable-sort by CellKey → at-most-one destination claim → stall losers no-loss), `MachineSlotMath` (byte-id deposit/withdraw/total, the non-replicated twin of `StorageMath`). +- **Systems** (`Server/Automation/`, server-only, ordered `[UpdateAfter(PredictedSimulationSystemGroup)]` chain Harvester→Conveyor→Fabricator): `HarvesterProductionSystem` (fixed-yield → its `MachineOutput`), `ConveyorTransportSystem` (pull from adjacent upstream `MachineOutput` → `ResolveMoves` → settle `ConveyorItem` enable-bit / deposit to sink `MachineInput`), `FabricatorProductionSystem` (input-limited recipe → GLOBAL ledger). `Tuning.MaxProductionCatchup = 600`. +- **Persistence (SaveData v2):** `StructureSave[]` + flat `StructureIoRow[]` (cooldown as epoch-independent REMAINING ticks; in-flight conveyor item inline); `SaveComponents` `PendingStructure`/`PendingStructureIo` staging buffers; **shared `SaveStructureScan.Collect`** (both autosave + quit-save scan ONLY `RuntimePlacedTag` structures → kills the two-path drift the critic flagged); `SaveWriteSystem` + `WorldLauncher` (StagePendingSave + TrySaveFromServer) extended; new **one-shot `BaseRestoreSystem`** (gates on `StructureCatalog`+`BaseAnchor`+`NetworkTime`, replays each saved structure CHARGE-FREE, refills buffers + conveyor item, re-tags, self-destructs). +- **Build wiring:** `BuildPlaceRequest` +`byte Direction` (additive scalar; RPC wire-hash shifts → single-build); `BuildPlaceSystem` stamps conveyor `Direction` (via a `ComponentLookup` on the prefab) + `RuntimePlacedTag` + `LastProcessedTick=0`; `BuildSendSystem` H/F/C keys + `[`/`]` conveyor-rotate + editor statics `PlaceHarvester/Fabricator/Conveyor`. +- **Authoring + prefabs:** 3 MonoBehaviours + bakers; `StructureCatalogAuthoring` +3 rows (Harvester 20 / Fabricator 30 / Conveyor 2 Ore); `Harvester/Fabricator/Conveyor.prefab` (duplicated from `Turret.prefab` → swapped authoring; ownerless-interpolated `GhostAuthoringComponent` + mesh free) wired into the catalog in the Gameplay subscene. + +## Validation + +- **EditMode 190/190** (35 new automation tests): catch-up gating/clamp/no-mint/period-0-guard; conveyor Y-junction tie-break + 4-cell line + shuffle-invariance + blocked-stall; fabricator input-limit (no mint-from-nothing); SaveData v2 round-trip + epoch-independent cooldown. +- **Runtime (real netcode ServerWorld+ClientWorld, focused editor, via `execute_code`):** catalog bakes all 6 entries (T1/T5/T6 + **T2/T3/T4 machine prefabs valid**); placed a live H(18,18)→C(19,18 +X)→F(20,18) chain (RPC → cost Ore 200→148); the chain **ran end-to-end** (Harvester output flowed → Conveyor transported → Fabricator `2 Ore → 1 Aether` → ledger), **SERVER Aether == CLIENT Aether** (22, then 52 — replicated). **Quit autosaved** v2 (3 structures + rebased `RemainingTicks`); **Continue restored** all 3 at the exact cells, **charge-free** (Ore unchanged at 148), born-correct ledger, and the **restored chain resumed producing**. Console clean of errors throughout. + +## Decisions + +[[DR-020_M7_Automation_Production_Chains]]. Resolves the open [[Backlog]] "production replication (predicted vs server-only)" question: **server-only** — production runs in the plain server `SimulationSystemGroup`; results replicate via the existing global ledger + `PlacedStructure` ghosts, never predicted. + +## Next-session intent + +- **Throughput visuals** (item-on-belt): server-only machine buffers don't reach the client (clients see only `PlacedStructure.Type`); add ONE small replicated byte if live belt visuals are wanted. +- **Build-palette HUD + ghost preview** + a conveyor-facing indicator (dev `[`/`]` rotate + H/F/C keys only today). +- **Relevancy ceiling:** `RegionRelevancySystem`'s O(structures×connections)/tick scan becomes load-bearing at higher conveyor counts (DR-014 ceiling note) — batch it when counts grow. +- **Recipe depth:** multi-input fabricator recipes; fabricator→conveyor output chaining (additive — give the fabricator a `MachineOutput`); per-machine distinct meshes (machines reuse the Turret mesh as a placeholder). +- **Operator live play-through** + a real 2-build LAN run to exercise the chain under latency (single-client validated). diff --git a/Docs/Vault/07_Sessions/2026/2026-06-06_Cleanup_Alignment.md b/Docs/Vault/07_Sessions/2026/2026-06-06_Cleanup_Alignment.md new file mode 100644 index 000000000..4bc5458e7 --- /dev/null +++ b/Docs/Vault/07_Sessions/2026/2026-06-06_Cleanup_Alignment.md @@ -0,0 +1,53 @@ +--- +date: 2026-06-06 +type: session +tags: [session, cleanup, alignment, docs, refactor, hygiene, connectionui, uitk] +--- + +# Session 2026-06-06 — Cleanup & alignment (docs, dead code, refactor) before more development + +## Goal + +Operator: *"Start a cleanup session — clean up documentation code and align everything in the project so it's ready for more development."* Run via `/dots-dev` (ultracode). + +Three large 2026-06-05 sessions (M7 Automation, Game-infrastructure menu/settings/saves, HUD rework to UITK) were done but **uncommitted**, with session logs + DRs already written. `CLAUDE.md` had drifted from the code (still described `SampleScene` as the gameplay scene; nothing on Automation / Saves / UITK / the frontend lifecycle), and the logs had flagged cleanup candidates (legacy IMGUI `ConnectionUI`, the `_Recovery/` artifact). + +Intake (AskUserQuestion): **deep refactor + docs** · **delete aggressively** · **group & commit for me** (authorizes git writes; commits land on `main`, matching the solo history; no push). + +## Process + +- **Phase 0–1 (read-only scan):** editor-state + git + vault scan; a 3-agent Explore swarm (code/asset audit · doc-delta audit · dead-asset + git-grouping audit). Verified the risky findings myself before acting: `ConnectionUI` has **zero code deps** (all hits were `ConnectionUI` doc-comments); `_Recovery` = 2 tracked + 1 untracked recovery scenes; **no new asmdefs** (the new feature folders live inside the existing four — corrected an agent's wrong "add `ProjectM.Automation`/`Persistence` rows" suggestion). +- **Plan-gate:** scoped "deep refactor" to **behavior-preserving** (DRY / dead-code / readability) — explicitly NOT rewriting working netcode/sim; the determinism-critical paths (`ProductionMath.CyclesDue`, `ConveyorMath.ResolveMoves`, `BaseRestoreSystem`) were left untouched. Approved → executed A→F. + +## Done + +### A — Dead-asset deletion (via `manage_asset`, AssetDatabase-consistent) +- Deleted `Assets/_Recovery/` (3 crash-recovery scenes + metas; zero references, not in build settings) and the stray empty `Assets/Resources.meta` (top-level `Assets/Resources/` was empty). Kept `Assets/_Project/Resources/` (`RuntimePanelSettings` + theme) and `Assets/UI Toolkit/UnityThemes` (UITK default scaffolding). + +### B — Removed the legacy IMGUI `ConnectionUI` +- Removed the standalone `NetConnectionUI` GameObject from `Game.unity`, `DevSandbox.unity`, `SampleScene.unity` (each only held Transform + `ConnectionUI`); deleted `ConnectionUI.cs` (+ meta); confirmed zero remaining GUID references in any scene. Repointed the 4 stale doc-comments (`GameBootstrap`, `DebugOverlay`, `EditorAutoHostSystem`, `ConnectionConfig`) from `ConnectionUI` to the UITK `MainMenuController` / `WorldLauncher` (via `script_apply_edits anchor_replace`). The UITK frontend menu owns Host/Join+IP everywhere now. + +### C — Targeted behavior-preserving refactor (all via MCP, verified vs live file) +- **DRY'd the duplicated UITK `Round`/`Border` helpers:** `MenuUi` is now the canonical home (made `public`); `HudUi`'s byte-identical copies deleted and its 2 internal call sites + `HudSystem`'s 3 call sites repointed to `MenuUi.*`. `HudUi` is now a genuine thin extension. +- **`HudSystem`:** cache `_huskQuery.CalculateEntityCount()` once/frame (was called twice). +- **`BuildSendSystem`:** replaced the 6 near-identical hotkey `if` branches (drifting `cell`/`wcell`/… vars) with a `static readonly (Key, byte)[] s_BuildHotkeys` table + one loop — adding a buildable is now one row. +- Deliberately **skipped** low-value/higher-risk tidies (SettingsService clamp order, ConveyorTransport struct-array, WorldFeedback lookup, PlayerInput dedup) → logged as a [[Backlog]] follow-up. + +### D — Documentation alignment +- **`CLAUDE.md`** (30.7 KB → 36.4 KB, still < the 40 KB limit): rewrote **Bootstrap & worlds** (`AutoConnectPort=0`, editor-instant-play vs frontend toggle, MainMenu(0)/Game(1) scene split, on-demand `WorldLauncher` lifecycle + subscene-streams-into-on-demand-world rule); added an **M7 Automation** + **disk-persistence** bullet to Build/structures (replacing the now-false "deferred to post-M7"); replaced the "uGUI HUD" claim with a **UITK HUD + build-palette** bullet; added build-gotchas (frontend world lifecycle, `CreateLocalWorld` internal, don't-dispose-worlds-in-a-system, URP `m_AssetVersion`-from-the-future, raw-`Write` new-`.cs`-needs-force-refresh, `script_apply_edits` anchor/delete_method on ISystems); noted the new feature folders fit the **existing four asmdefs** (no new assemblies). +- **Vault:** `Milestones` (M6 + the 2026-06-04 polish pass → ✅ Done; added rows for the HUD rework + this cleanup), `Backlog` (ConnectionUI item → done; added a delivered-note + a cleanup-follow-ups section), `Home` + `Systems_Index` (date + latest-session pointer). New DR not warranted (hygiene, no new architecture). + +## Validation + +- **EditMode 194/194** (`ProjectM.Tests.EditMode`, 0.29 s) — same count as pre-cleanup → no regressions, no tests lost; proves the whole codebase (refactor + ConnectionUI removal + comment edits) compiles clean. +- **Play smoke (focused editor, real netcode):** `ServerWorld` + `ClientWorld` each `conns=1 players=1` (worlds boot → loopback connect → player spawn + replicate → subscene streamed fine after the scene edits); the **UITK HUD renders correctly** (`Cleanup_PlaySmoke.png`) — rounded bars (`MenuUi.Round`), the 6-buildable palette with borders (`MenuUi.Border` + the hotkey/palette path), "HUSKS 0" (cached count), and **no "Net: Connected" IMGUI label** (ConnectionUI gone). Console clean of project errors. +- **Note:** the only console "errors" are engine-internal `Assert`s — *"Access version should be odd when acquiring lock"* from `Modules/Audio/Public/DualThreadManager.h:197` — that fire on domain reload / in Play. They are NOT from project code (audio-thread engine noise), are Assert-level, and don't affect tests/runtime. Left as-is (not a cleanup-pass concern). + +## Decisions + +No DR (hygiene pass). Reaffirmed conventions: edit `Assets/*.cs` only via MCP; `script_apply_edits` `anchor_replace`/`delete_method` are safe for comment/method edits even on a `struct : ISystem`; `manage_asset delete` keeps the AssetDatabase consistent for asset removals. + +## Next-session intent + +- Resume feature work on the now-aligned, committed base. Candidate threads from the M7 follow-ups: throughput/belt visuals (one replicated byte), recipe depth (multi-input fabricator, fabricator→conveyor chaining), per-machine meshes; operator live play-through + a real 2-build LAN run. +- Optional code-tidy follow-ups (logged in [[Backlog]]) if churn budget allows. diff --git a/Docs/Vault/07_Sessions/_Decisions/DR-019_Frontend_Menu_Settings_Saves_Build.md b/Docs/Vault/07_Sessions/_Decisions/DR-019_Frontend_Menu_Settings_Saves_Build.md new file mode 100644 index 000000000..2915389d1 --- /dev/null +++ b/Docs/Vault/07_Sessions/_Decisions/DR-019_Frontend_Menu_Settings_Saves_Build.md @@ -0,0 +1,41 @@ +--- +id: DR-019 +title: Front-end infrastructure — Netcode frontend menu (on-demand worlds), UI Toolkit, Graphics/Audio settings, saves foundation, build wiring +status: accepted +date: 2026-06-05 +tags: +- decision +- netcode +- world-lifecycle +- frontend +- ui-toolkit +- settings +- saves +- build +permalink: gamevault/07-sessions/decisions/dr-019-frontend-menu-settings-saves-build +--- + +# DR-019 — Front-end infrastructure (menu, settings, saves, build) + +## Context + +The game had no shell: `GameBootstrap.Initialize` always created BOTH ServerWorld + ClientWorld and an editor-only auto-host dropped you straight into gameplay ([[DR-005_M4_Connection_Model_Direct_IP]]). To "build and test the game" as a product the operator asked for a **main menu** (Single/Host/Join), **settings that actually change the game**, **persistent saves**, and a **build pipeline**. Intake scoped: saves = foundation + a minimal real slice; UI = **UI Toolkit**; settings = **Graphics + Audio**; build = wire-up only. A 3-critic adversarial Workflow reviewed the design before coding (per the [[CLAUDE]] netcode-slice rule) and changed several load-bearing choices. Verified against context7 (Netcode 1.13.2) + runtime. + +## Decision + +1. **Netcode frontend pattern, builds vs editor.** Boot a menu with NO netcode worlds; create them **on demand** per menu choice via the public `ClientServerBootstrap.CreateServerWorld(name)` / `CreateClientWorld(name)` (which register the `ServerWorld`/`ClientWorld` statics — `ConnectionUI`/`DebugOverlay` depend on them). `CreateLocalWorld` is **internal** in 1.13.2, so the menu world is a plain default world (`Initialize` returns `false`, or `DefaultWorldInitialization.Initialize` on return-to-menu). The **editor default path is unchanged** (today's `CreateDefaultClientServerWorlds()` + MPPM + `EditorAutoHostSystem`); a `ProjectM/Boot Into Menu (Editor)` EditorPrefs toggle — gated to `RequestedPlayType==ClientAndServer` so MPPM virtual players never boot to the menu — flips the main editor to the frontend for in-editor testing. Player builds boot the menu (`Server` playtype auto-hosts). +2. **One world-lifecycle funnel, frame-boundary.** `WorldLauncher` (static) owns `StartSession(mode,ip,loadSave)` and `TeardownToMenu()`, run on a persistent `SessionRunner` (`DontDestroyOnLoad`) coroutine — never inside an ECS system. Start: dispose the menu world → create worlds (Single/Host=server+client, Join=client-only) → seed the existing `ConnectionConfig` (Single binds **loopback**, Host binds AnyIpv4) → set a netcode world as `DefaultGameObjectInjectionWorld` → `LoadScene(Game, Single)` so the **SubScene streams into the on-demand worlds**. Teardown: autosave (host) → `World.DisposeAllWorlds()` → `LoadScene(MainMenu, Single)`. **Reuses the whole connection layer unchanged** (`ConnectionConfig` + control systems + GoInGame + ring spread). +3. **UI Toolkit, code-built.** `MainMenuController` / `SettingsScreen` / `PauseMenuController` build their trees in C# with inline styles. A shared `RuntimePanelSettings.asset` (+ a `RuntimeTheme.tss` importing `unity-theme://default`) loaded from Resources, and an ensured `EventSystem` + `InputSystemUIInputModule` (the project uses the Input System), make runtime UITK render + receive input. +4. **Settings drive the engine.** `SettingsService.Apply` → `Screen.SetResolution`/`fullScreenMode`, `QualitySettings.SetQualityLevel`/`vSyncCount`, `Application.targetFrameRate`, and audio. **Master rides `AudioListener.volume`; `GameVolume.Music/Sfx` are per-bus trims the 3 client audio systems multiply per-call** (no double-attenuation). Persisted as versioned JSON (`JsonUtility`, atomic write) at `persistentDataPath`. `GameVolume` is deliberately NOT named `AudioSettings` (collides with `UnityEngine.AudioSettings`). +5. **Saves = born-correct, host-authoritative, additive.** Versioned single-slot JSON (`SaveData{GoalCharge,GoalTarget,LedgerRow[],SavedAtMs}`). Load is **applied at director spawn** (`CycleDirectorSpawnSystem` reads an unmanaged `PendingSave` singleton the menu staged in the ServerWorld) so the CycleDirector ghost is BORN with the saved GoalProgress + ledger and never replicates a default first. Autosave on the Siege→Calm checkpoint (Bursted `CyclePhaseSystem` raises a `SaveRequest` byte; managed host-only `SaveWriteSystem` writes) + on quit-to-menu. Schema is additive (structures/threat/storage append later behind `Version`). +6. **Build wiring only.** `Editor/BuildTool.cs` (`ProjectM/Build/Windows Player`, scenes = MainMenu+Game) + the editor toggle; `EditorBuildSettings` = `0:MainMenu 1:Game 2:DevSandbox 3:SampleScene`. The operator runs the actual standalone. + +## Consequences + +- **Validated end-to-end at runtime** (focused editor): menu renders → Single creates worlds, the **subscene streams into the on-demand worlds**, loopback connects, the player spawns + replicates, the base is playable → teardown disposes the netcode worlds + recreates the menu world + writes a save → Continue loads **born-correct** (`charge=42 ledger=[(1:15)] pending=0`). Settings apply live. EditMode 152/152. +- **The frontend pattern is transport-agnostic** like DR-005 — Unity Relay later swaps the endpoint source feeding `ConnectionConfig`, no change to the menu/lifecycle. +- **The save schema is frozen as the additive foundation** the [[CLAUDE]] "freeze the schema now" note asked for; the minimal slice (ledger + goal + settings) overrides the strict post-M7 deferral, but only additively. +- **Editor/build divergence is intentional and small:** the editor keeps the proven instant-play + MPPM loop by default (a toggle tests the menu); builds get the real menu. The bootstrap edit is last + the default path is byte-identical to before — no MPPM regression risk. +- **Deferred:** controls/rebinding, full base-state saves, Relay, dedicated-server build (defensive branch, untested), legacy IMGUI `ConnectionUI` removal (harmless, left to avoid touching the load-bearing `Game.unity` subscene), volume-slider theming fallback. + +Extends [[DR-005_M4_Connection_Model_Direct_IP]]; mirrors the server-authoritative / small-co-op pillars from [[Pillars]]. diff --git a/Docs/Vault/07_Sessions/_Decisions/DR-020_M7_Automation_Production_Chains.md b/Docs/Vault/07_Sessions/_Decisions/DR-020_M7_Automation_Production_Chains.md new file mode 100644 index 000000000..3848525be --- /dev/null +++ b/Docs/Vault/07_Sessions/_Decisions/DR-020_M7_Automation_Production_Chains.md @@ -0,0 +1,52 @@ +--- +id: DR-020 +title: M7 Automation — server-only deterministic production chains (Harvester → Conveyor → Fabricator) + SaveData v2 structure persistence +status: accepted +date: 2026-06-05 +tags: +- decision +- netcode +- automation +- production +- conveyor +- determinism +- persistence +- m7 +permalink: gamevault/07-sessions/decisions/dr-020-m7-automation-production-chains +--- + +# DR-020 — M7 Automation: server-only deterministic production chains + SaveData v2 structure persistence + +## Context + +M6/DR-014 locked the structure model as "the M7 contract": `PlacedStructure{[GhostField] byte Type; int2 Cell; uint NextTick; uint LastProcessedTick}` (tick fields baked as the offline-catch-up linchpin), a data-driven `StructureCatalog`, occupancy-derived placement, and reserved type codes `Harvester=2/Fabricator=3/Conveyor=4`. M7 ([[Pillars]] automation pillar) builds the self-running chain on top. The operator chose the FULL chain (Harvester → Conveyor → Fabricator), auto-gathering EXISTING resources (no new ids), with persistence folded in. A 3-lens adversarial Workflow (code · persistence/authoring · netcode/determinism critic) reviewed the design before coding (per the [[CLAUDE]] netcode-slice rule) and changed several load-bearing choices. Verified against context7 (Entities 6.4 / Netcode 1.13.2) + EditMode (190) + runtime (real netcode worlds). See [[2026-06-05_M7_Automation]]. + +## Decision + +1. **Production is SERVER-ONLY** (resolves the open [[Backlog]] "predicted vs server-only" question). Three `ISystem`s in the plain server `SimulationSystemGroup`, ordered `[UpdateAfter(PredictedSimulationSystemGroup)]` → `HarvesterProductionSystem` → `ConveyorTransportSystem` → `FabricatorProductionSystem` (a one-tick chain). No prediction, no rollback, no `Simulate` filter. Results reach clients ONLY through already-replicated state: the global `StorageEntry` ledger ([GhostField] on the untagged CycleDirector ghost) and `PlacedStructure.Type`. Per-machine I/O is **server-only buffers** (`MachineInput`/`MachineOutput` — DISTINCT element types, NO `[GhostField]` → never hit the wire; resolve the ledger via `GetSingletonEntity()`, NEVER `GetSingleton`). + +2. **The chain.** `Harvester{byte ResourceId;int Yield;int PeriodTicks}` = a fixed-yield base generator (NOT node-tapping — decoupled from the per-cycle despawning expedition field) depositing into its own `MachineOutput`. `Conveyor{byte Direction;int PeriodTicks}` pulls one item off the adjacent upstream `MachineOutput` (cell − `DirOffset`) when its single `ConveyorItem` (enableable, baked disabled) is empty, then advances exactly one cell toward `Direction`. `Fabricator{In/OutResourceId,In/OutAmount,PeriodTicks}` consumes its `MachineInput` and deposits the recipe output into the GLOBAL ledger (the "auto-gather existing resources" terminal; default 2 Ore → 1 Aether). The DR-014 "recipe column on `StructureCatalogEntry`" was **dropped as dead weight** — the recipe is baked on each machine component (the catalog stays cost+prefab only). + +3. **Single gated catch-up path** (`ProductionMath`, pure + unit-tested). One path per machine: if `NextTick==0` (uninitialized/just-placed) → schedule (`Last=now`, `Next=now+period`), produce nothing; else if cooling → 0; else `cycles = clamp(TicksSince(Last)/max(1,period), 0, MaxCatchup)`. **Lower bound 0, not 1** (the critic's `1` would mint a premature cycle on a restored `remaining==0` machine where `since=period` in the normal path). `period=math.max(1,…)` + `[Min(1)]` kills div-by-zero; the fabricator additionally clamps `runs=min(cycles, floor(input/InAmount))` (no mint-from-nothing). `BuildPlaceSystem` now stamps `LastProcessedTick=0` (was `NonZero(now)`) so runtime-placed machines initialize cleanly. + +4. **Deterministic, order-independent conveyor** (`ConveyorMath.ResolveMoves`, pure + exhaustively unit-tested). Sources iterate sorted by a stable `CellKey` (NEVER hashmap order); destination occupancy is read from a PRE-move snapshot (double-buffer → exactly one cell/tick); a belt cell accepts AT MOST ONE item; ties break to the lowest-CellKey source; losers STALL with no loss; machine-input SINK cells always accept (merge). Tested with a Y-junction (deterministic winner) + a 4-cell line + **shuffle-invariance** (identical end-state under two input orders) + a blocked-cell stall. + +5. **SaveData v2 = structures fold in; cooldowns as epoch-independent REMAINING ticks; "offline catch-up" is within-session only.** v2 adds `StructureSave[]` (type, cell, direction, `RemainingTicks`, in-flight conveyor item) + a flat `StructureIoRow[]` table (JsonUtility can't nest arrays-of-arrays; joined by index). Cooldown is persisted as **remaining ticks** (`NextTick−now`), not an absolute tick, so it survives the server-tick origin reset on a fresh session (sidesteps the negative-rebase blocker). On restore `LastProcessed=now` → the chain RESUMES; no production is minted for wall-clock time spent quit (the determinism pillar forbids it — the preserved stockpile, not a clock, is what compounds). `SaveService.Load` already nulls on a version mismatch, so a pre-M7 (v1) save cleanly degrades to New Game. + +6. **One-shot, charge-free restore** (`BaseRestoreSystem`, server). The menu stages `PendingStructure`/`PendingStructureIo` on a SEPARATE carrier (the `PendingSave` ledger carrier is consumed+destroyed by `CycleDirectorSpawnSystem`); `BaseRestoreSystem` gates on `StructureCatalog`+`BaseAnchor`+`NetworkTime` (dodges the subscene-stream race), instantiates each saved structure from the catalog at `CellToWorld(cell)` (preserving baked Scale), re-stamps the rebased ticks, re-adds `RegionTag{Base}`+`RuntimePlacedTag`, refills buffers + conveyor item, and **destroys its carrier (one-shot)**. NEVER `Withdraw`s — the ledger restores absolutely via the existing born-correct path. A `RuntimePlacedTag` (added by `BuildPlaceSystem` + restore) is the **single source of truth**: only player-built structures are saved/restored; anything baked into the subscene stays the subscene's. + +## Consequences + +- **Validated at runtime on 6.4.7 (real ServerWorld+ClientWorld, single in-editor client):** catalog bakes all 6 entries (machine prefabs valid ghosts); a placed H→C→F chain ran end-to-end (Ore 200→148 cost; Harvester→Conveyor→Fabricator→ledger), **server Aether == client Aether** (replicated); quit autosaved v2 (3 structures + cooldowns); Continue restored all 3 at the exact cells, **charge-free** (Ore unchanged), born-correct ledger, and the **chain resumed producing**. EditMode **190/190**. Console clean. +- **No new asmdef.** New code under `…/Automation/` (Simulation/Server/Authoring) + persistence edits in `…/Persistence/`. Reuses `BuildPlaceSystem` placement, `StorageMath` + the global ledger, `BaseGridMath`, `TickUtil`, the `TurretFireSystem` NetworkTick-cooldown idiom, the duplicate-prefab ghost recipe, the byte-RPC pattern, and the born-correct save/load path. +- **Process incident:** the source-drafting Workflow's general-purpose agents raw-`Write`-wrote files into `Assets/`, which compile but get no `.meta`/test-discovery until a `scope=all mode=force` refresh — EditMode silently ran the old count (152) until forced. Captured as [[raw-written-cs-needs-full-refresh]]; future no-Unity-write workflows should use a read-only agentType. + +## Open / deferred + +- **Throughput visuals** (item-on-belt) need a small replicated field — server-only buffers don't reach the client (clients see only `Type`); accept static visuals or add one byte. +- **Build UX:** a build-palette HUD + ghost-preview + conveyor-facing indicator (dev H/F/C keys + `[`/`]` rotate today). +- **Relevancy ceiling:** `RegionRelevancySystem`'s O(structures×connections)/tick scan (DR-014 note) becomes load-bearing at higher conveyor counts — batch it when counts grow. +- **Recipe depth:** multi-input fabricator recipes; fabricator→conveyor output chaining (additive — add a `MachineOutput` to the fabricator); per-machine distinct meshes (placeholder Turret mesh today). +- **Multi-client + LAN:** the chain + replication are single-client validated; pairs with the deferred 2-build LAN tests. + +Builds on [[DR-014_M6_Build_Structures_Automation_Foundation]] (the structure model + tick fields this extends), [[DR-019_Frontend_Menu_Settings_Saves_Build]] (the born-correct save/load path), [[DR-013_M6_Aether_Cycle_Region_Split]] (ledger/region), and [[DR-008_M5_HomeBase_BaseLayer_Storage]] (grid/RPC/runtime-ghost). Serves the automation pillar in [[Pillars]]. diff --git a/Docs/Vault/07_Sessions/_Decisions/DR-021_HUD_UITK_BuildPalette.md b/Docs/Vault/07_Sessions/_Decisions/DR-021_HUD_UITK_BuildPalette.md new file mode 100644 index 000000000..a741a7dfb --- /dev/null +++ b/Docs/Vault/07_Sessions/_Decisions/DR-021_HUD_UITK_BuildPalette.md @@ -0,0 +1,44 @@ +--- +id: DR-021 +title: In-game HUD rebuilt on UI Toolkit (consistent with the menu) + a click-to-place build-palette HUD with ground ghost preview +status: accepted +date: 2026-06-05 +tags: +- decision +- hud +- ui-toolkit +- build +- presentation +- juice +permalink: gamevault/07-sessions/decisions/dr-021-hud-uitk-build-palette +--- + +# DR-021 — UI Toolkit HUD rework + build-palette HUD (click-to-place + ghost preview) + +## Context + +The frontend (menu / settings / pause) moved to UI Toolkit in DR-019, but the in-game HUD was still code-built **uGUI** (`HudSystem`: RawImage bars over `Texture2D.whiteTexture` + legacy `Text`) — two visual languages. M6/M7 added buildable structures but the only build UX was dev hotkeys (H/F/C/B/V/N at the player's cell). The operator: *"build palette hud — rework all the hud … use the new uitoolkit so it is consistent."* Intake: build palette = full click-to-place + a ground ghost preview; also re-skin the floating combat juice. Verified against the existing UITK infra + runtime. + +## Decision + +1. **HUD → UI Toolkit, observe-only, System-owned UIDocument.** Rewrite `HudSystem` (client `PresentationSystemGroup`) to own a runtime `UIDocument` directly (the same shape the old uGUI HudSystem used for its Canvas) rather than a separate controller MonoBehaviour — it reads ECS every frame, so a System pushing values into held `VisualElement` refs is simpler than a setter bridge. Shared `MenuUi.LoadPanelSettings()` (the menu's `RuntimePanelSettings`) at **`sortingOrder=50`** (the pause overlay is 100, so it layers on top). The tree is built on the **first OnUpdate where `rootVisualElement` exists** (the panel needs a frame to init its PanelSettings after `AddComponent`). New `HudUi` factories (percent-width bar fill, bold labels) extend `MenuUi`'s Aether palette so HUD + menu read identically. +2. **The HUD never eats game clicks.** The root + every non-interactive element is `pickingMode = PickingMode.Ignore`; only the build-palette buttons opt back in (`Position`). This is the load-bearing UITK-in-game rule — a default-picking full-screen panel silently swallows every world click. The pause overlay (sortingOrder 100, full-screen, picking) already modally covers the HUD when open. +3. **Build palette = client-catalog-driven, observe-only.** The palette is lazy-built from the **client-side `StructureCatalog`** (the gameplay subscene streams into both worlds, so the client has Type + cost without replication) and shows affordability from the replicated ledger. A palette-button click sets client-local `BuildPaletteState.Selected` (UI state, never the sim) — the HUD stays a pure observer. +4. **Click-to-place + ground ghost in `BuildSendSystem` (client sim), not presentation.** Placement sends the RPC, so it lives in the `ClientSimulation` build system, not the observe-only HUD. When a buildable is selected it raycasts cursor→ground→cell (`AimMath.TryGroundHit` + `BaseGridMath.WorldToCell`) and drives a procedural translucent **ghost cube** coloured by `BuildPreviewMath.Evaluate` (pure, unit-tested: in-plot → unoccupied → affordable, the client mirror of the server's `BuildPlaceSystem` check; occupancy derived client-side from the replicated `PlacedStructure` ghosts' positions, since `Cell` is server-only). Left-click places (the existing `BuildPlaceRequest` path), right-click/Esc cancels, `[`/`]`/R rotates a conveyor. The one-frame cursor lag of reading the camera in ClientSimulation (before its LateUpdate move) is imperceptible for cell-snapped base building. +5. **Interaction guards are self-contained.** Fire is suppressed while build mode is active (`PlayerInputGatherSystem` gates `Fire` on `!BuildPaletteState.Active`) so the place-click never fires. The frame the selection changes never also places (`_lastSelected` compare in BuildSendSystem — robust to either UITK-event/ECS ordering). Build mode is suspended while `PauseMenuController.Open` so pause-menu clicks can't place. `AimPresentation.ForceCursorVisible` shows the cursor in build mode (the aim system hides it while aiming). +6. **Juice restyle, not rebuild.** The floating damage numbers (world-space pooled `TextMesh`) stay world-space (UITK is screen-space) but are re-skinned to the Aether palette + bold: Blight-orange when the local player is hurt, Aether-cyan when you damage a Husk. The aim reticle / VFX / dev overlays are out of scope. + +## Consequences + +- **Validated at runtime on 6.4.7** (real ServerWorld+ClientWorld, focused editor): the UITK HUD renders every panel in the Aether palette (`~HUD` UIDocument, 5 root groups); the palette shows all 6 buildables + costs; selecting one highlights it + activates the ground ghost at the cursor cell (green/red); clearing hides it; placement through the build RPC works; console clean. **EditMode 190→194.** Screenshots: `HudRework_Base.png`, `HudRework_BuildMode.png`. +- **No new asmdef, no netcode surface change** — reuses the M7 `BuildPlaceRequest` path, `MenuUi`/PanelSettings, `AimMath`/`BaseGridMath`/`AimPresentation`. New code: `HudUi`, `BuildPaletteState`, `BuildPreviewMath` (+test); rewrite `HudSystem`; extend `BuildSendSystem`; 1-line `PlayerInputGatherSystem`; `PauseMenuController.Open`; `CombatFeedbackSystem` restyle. +- Resolves the [[Backlog]] M7 follow-up "Build-palette HUD + ghost preview". + +## Open / deferred + +- Per-buildable **icons** + a conveyor-facing **arrow** on the ghost (text palette + plain cube today). +- **Throughput visuals** (item-on-belt) need a small replicated field — server-only machine buffers don't reach the client (the DR-020 open item). +- Remove the legacy IMGUI `ConnectionUI` "Net: Connected" label — the only non-UITK on-screen UI left in-game. +- A build-mode HUD hint line + an open/close palette focus key. + +Builds on [[DR-019_Frontend_Menu_Settings_Saves_Build]] (the UITK frontend + PanelSettings + pause pattern) and [[DR-020_M7_Automation_Production_Chains]] / [[DR-014_M6_Build_Structures_Automation_Foundation]] (the structures + catalog + `BuildPlaceRequest` this builds the palette UX on). Serves the [[Pillars]] co-op base-building pillar.