Compare commits

...

10 Commits

Author SHA1 Message Date
kronic 4fa2cb0de4 Docs: DR-051 + session log — LANTERN realignment & purge bookend
DR-051 (delete-not-mothball, four-scene contract, one look, Bathynaut in),
session log w/ commit table + field notes, strip/mothball inventory status
banner (MOTHBALL rows -> DELETED), CLAUDE.md updated (scene contract, DR-051
purge bullet, LANTERN murk world bullet, save v7, retired bullets archived
07-15; 39.8 KB, under budget).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 16:37:14 -07:00
kronic 210d307406 LANTERN realignment P5: Bathynaut suit body onto Player.prefab (in place)
New PlayerRigTools.BuildBathynautPlayer (menu: ProjectM/Animation) - forks the
proven EnemyRigTools recipe: strips the Synty-soldier skeleton+SMRs from
Player.prefab IN PLACE (GUID preserved; ghost/authoring/CC/Animator/
RigDefinitionAuthoring untouched), flattens the SpaceSoldier_HelmetArmor suit
body under the root, keeps the -0.90 feet offset, re-slots
M_SuitFrame_Bathynaut_Animated on every SMR, adopts the shared CharactersAvatar
(AC_PlayerTopDown muscle clips retarget). 17 inactive Synty variant SMRs stripped.
Both frames share this visual until per-frame models land; the brass-dome/tank
kitbash attachments are a follow-up /art-dev export.

Play-verified in DevSandbox: suit renders + idles correctly under the LANTERN
murk, Rukhanka rig baked with all 9 drive params, dev overlay alive in the
renamed scene, no console errors.

=> Movement/animation tuning is now UNBLOCKED (the operator's next priority).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 16:31:07 -07:00
kronic 12d5b96b71 LANTERN realignment P4: Gameplay subscene -> seabed arena roster + old enemy prefabs deleted
- WaveDirector + ZoneEnemyDirector rosters re-pointed (ORDER-CRITICAL before prefab
  deletion: null-skip pools collapse kinds): [Drowner, Grindylow, Drowner, Drowner]
  (grunt/charger(boss)/spitter-slot/swarmer-slot; ranged+pack kinds fall back to
  Drowner until LANTERN kinds land in Phase 2).
- WorldColliders_Landmarks + WorldColliders_Props deleted from the subscene (their
  visuals died with the host-scene biomes -> they were invisible walls). Boundary
  rings + room cover + all spawners/configs kept.
- 10 old-theme enemy prefabs deleted (Enemy, Brute, Charger, ChargerMuscle,
  Spitter, Swarmer(+Undead), Werewolf(+Undead), Kaiju); EnemySpit kept for a
  future ranged kind. Zero dangling GUID refs (verified).

Play-verified in Game: Drowner wave spawns, seeks, strikes; AFK player dies and
respawns (full loop); no console errors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 16:24:31 -07:00
kronic c58439d33a LANTERN realignment P3: one look everywhere + scene surgery
- PostFX_Lantern.asset: the ONE shared grade (ACES, bloom 0.6 cool-tinted for the
  HDR gloam, vignette 0.28, faint teal filter) applied to Game, DevSandbox, and
  ArtStaging; PostFX_DarkSciFi / PostFX_Daylight / Sky_DaytimeProcedural deleted.
- Env_SeabedKit.prefab: ArtStaging's environment (seabed, flora, rocks, marine
  snow, caustics, warm pool, gloam fill, key light, StagingAmbiance animator)
  prefab-ized and placed in Game + DevSandbox; ArtStaging connected to it.
- Scene surgery: old DevSandbox.unity deleted; Gym.unity RENAMED DevSandbox.unity
  (GUID preserved -> GymSub wiring + the F1/F2 dev scripts that gate on the
  'DevSandbox' name come back to life). SyntyWorld root deleted from DevSandbox;
  BaseBiome/ExpeditionBiome/Slot1 deleted from Game.
- RenderSettings unified: no skybox, Exp2 teal fog {0.02,0.10,0.12} @ 0.035, flat
  near-black ambient {0.03,0.055,0.08}; camera clearFlags -> solid deep-water.
- ScenePolicy.IsGameplayScene() replaces the six scene.name=="Game" string gates
  (Game + DevSandbox share the dynamic look; menu/ArtStaging untouched).
- WorldAtmosphereSystem rewritten as water-column murk (LANTERN defaults; biome
  variants re-meant to kelp/trench/gloam-bloom; Ground_Arid tint block deleted).

390 green; Play-verified in Game: murk values live, warm-vs-cold reads, no errors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 16:12:00 -07:00
kronic 77de740b63 LANTERN purge B7: rename CharacterId -> FrameKind (frame terminology)
Enum renamed in StatIds.cs (byte values unchanged - serialized definitions/saves
never re-mean); all call sites + doc mentions swept (ClassTraits, PlayerAuthoring,
CharacterStatsDefinition field type, menu/UI, tests). CharacterStatsDefinition SO
CLASS name kept (asset-binding risk; deferred per plan). 390 green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 15:51:48 -07:00
kronic 4a8220ad3e LANTERN purge B6: delete the legacy single-ability path (sockets are THE ability model)
AbilityRef, AbilityCooldown, EffectiveAbilityStats, DefaultAbility deleted.
GoInGameServerSystem seeds the per-frame 4-socket Spark loadout UNCONDITIONALLY
(was gym-only); ClassSelectReceiveSystem + DebugOp.SetClass swap FrameId +
re-seed sockets + zero SocketCooldown; ClassSwapUtil.Apply drops newAbilityId;
EquipSystem weapons become stat-sticks (GrantedAbilityId removed from the item
blob/authoring); StatRecomputeSystem folds CharacterStatsRef + sockets only;
HUD cooldown bar reads socket 0 of SocketCooldown/EffectiveSocketStats; class
HUD readers are FrameId-only (ClassForAbility/AbilityFor deleted);
DebugModifierInjectionSystem drops CycleAbility.

390 tests green; Play-verified: a non-gym spawn gets frame=2 with Sparks
[7,8,6,9] and no console errors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 15:47:12 -07:00
kronic b34945c2d2 LANTERN purge B3+B5: delete the cycle/core/win-lose spine + onboarding; save epoch v7
Deletes CyclePhaseSystem, GoalReachedSystem, CoreDamage/CoreRestore, ThreatDirector,
CoreIntegrity/GoalProgress/RunPhase/RunOutcome/ThreatState components,
CoreVisualFeedbackSystem, and the whole Client/Onboarding slice (+6 test files).

Keepers reworked: RunDirectorSystem (UpdateBefore attr + launch guard + goal/threat
bank removed; sole SaveRequest raiser now), CycleDirectorSpawnSystem (ledger/meta
host only), WaveSystem UNGATED (waves run wherever a WaveDirector is baked),
EnemyAISystem core-fallback stripped, AmbientAudioSystem reworked (bed + run cues;
no CycleState gate), MusicSystem RunInfo-only, HudSystem big trim (goal meter, core
bar, siege banner, terminal banner, outcome flash, onboarding hook all gone),
MetaShop/ClassPrep/AimReticle siege gates dropped, DebugOverlay/ops re-meant
(SpawnWave=force next wave, EndSiege=quiet arena; SetCalm/AdvanceGoal/SetHeat
retired, bytes reserved), TuningConfig Core knobs retired (ids 20-23 reserved),
StorageMath.DrainFraction deleted, HowToPlay copy rewritten.

Save epoch v7 (fresh epoch, operator-approved): SaveData drops goal/core/outcome +
conveyor/machine-IO fields; MinLoadableVersion=7; PendingSave/PendingStructure
trimmed; RollTerminalCampaignForward deleted; SaveStructureScan signature slimmed.

390 tests green; Play world-creation clean (player + waves live, no exceptions).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 15:27:12 -07:00
kronic 835dace213 LANTERN purge B2: delete EB-2 turret defense
TurretFireSystem, TurretAuthoring, Turret.prefab, Turret component, turret cap,
B hotkey, HUD Charge chip + out-of-ammo cue, catalog entry, Tuning consts, tests
(-7, 452 green). StructureType.Turret byte + ResourceId.Charge stay reserved.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 14:40:12 -07:00
kronic a0f6d4a5c4 LANTERN purge B1: delete the automation chain (Harvester/Conveyor/Fabricator)
Deletes the M7 production systems, automation components/math, authoring, 3
machine prefabs, and 6 test files (-43 tests, 459 green). Trims the automation
paths out of BaseRestoreSystem/SaveStructureScan/BuildPlaceSystem/BuildSendSystem/
HudSystem/HudTheme/StructureCatalogAuthoring/Tuning. RuntimePlacedTag (save
marker, a keeper) re-homed into StructureComponents.cs. StructureType byte codes
stay reserved.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 14:32:11 -07:00
kronic 511e78556b Checkpoint: pre-realignment working tree (Gym->GymSub repoint + SyntyWorld/PostFX re-enable, SampleScene removed, PostFX_Daylight retune, ai.assistant pkg re-added)
Baseline before the LANTERN realignment & purge (plan: one look, four scenes,
Bathynaut on the player). Gym.unity edits are operator-made in-editor.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 14:16:00 -07:00
217 changed files with 10895 additions and 134511 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 1d4b5e92ac7b6ea4880d30545df94704
guid: 48885d98d8dbc3c47943402d0a455f21
DefaultImporter:
externalObjects: {}
userData:
+38 -79157
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-7
View File
@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 48885d98d8dbc3c47943402d0a455f21
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
-7
View File
@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 99c9720ab356a0642a771bea13969a05
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: c51a770922f68b046b12dc55a7f054c2
guid: 0ebe15ac3d1a57d45ac9492075ce1fdb
folderAsset: yes
DefaultImporter:
externalObjects: {}
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 4eba886d11c07eb4d97ca0d821a1560f
guid: 853083b81213dfe438063c6d6d41e564
folderAsset: yes
DefaultImporter:
externalObjects: {}
File diff suppressed because it is too large Load Diff
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 91f9551ef855b3b4f98bcfcb7bf747e2
guid: a56e554160baf224282f9467d6a5dfa0
PrefabImporter:
externalObjects: {}
userData:
-147
View File
@@ -1,147 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &3885353946372160549
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3572766465862231365}
- component: {fileID: 3909651526955663392}
- component: {fileID: 3320445911748035220}
- component: {fileID: 9053853372340598254}
- component: {fileID: 6834786618115927220}
- component: {fileID: 2432281660239002494}
m_Layer: 0
m_Name: Conveyor
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &3572766465862231365
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 2.5, y: 2.5, z: 2.5}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!33 &3909651526955663392
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Mesh: {fileID: 4300000, guid: abc00000000010690097314383055197, type: 3}
--- !u!23 &3320445911748035220
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: c01dbe8a4e818c5469eceae8b286bf4d, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 1
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_MaskInteraction: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!114 &9053853372340598254
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: c16549610bfe4458aa9389201d072bb6, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.Entities.Hybrid::Unity.Entities.Hybrid.Baking.LinkedEntityGroupAuthoring
--- !u!114 &6834786618115927220
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 7c79d771cedb4794bf100ce60df5f764, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.NetCode.Authoring.Hybrid::Unity.NetCode.GhostAuthoringComponent
HasOwner: 0
SupportAutoCommandTarget: 1
TrackInterpolationDelay: 0
GhostGroup: 0
UsePreSerialization: 0
UseSingleBaseline: 0
RollbackPredictedSpawnedGhostState: 0
RollbackPredictionOnStructuralChanges: 1
DefaultGhostMode: 0
SupportedGhostModes: 3
OptimizationMode: 0
Importance: 1
MaxSendRate: 0
prefabId:
--- !u!114 &2432281660239002494
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 23131e6166dce204582bbedc8511658e, type: 3}
m_Name:
m_EditorClassIdentifier: ProjectM.Authoring::ProjectM.Authoring.ConveyorAuthoring
Direction: 0
PeriodTicks: 20
-151
View File
@@ -1,151 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &3885353946372160549
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3572766465862231365}
- component: {fileID: 3909651526955663392}
- component: {fileID: 3320445911748035220}
- component: {fileID: 9053853372340598254}
- component: {fileID: 6834786618115927220}
- component: {fileID: 2544095781123180609}
m_Layer: 0
m_Name: Enemy
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &3572766465862231365
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 0.8, y: 0.8, z: 0.8}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!33 &3909651526955663392
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &3320445911748035220
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: fc4f991205d0aa347b65d89045f80c70, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 1
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_MaskInteraction: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!114 &9053853372340598254
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: c16549610bfe4458aa9389201d072bb6, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.Entities.Hybrid::Unity.Entities.Hybrid.Baking.LinkedEntityGroupAuthoring
--- !u!114 &6834786618115927220
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 7c79d771cedb4794bf100ce60df5f764, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.NetCode.Authoring.Hybrid::Unity.NetCode.GhostAuthoringComponent
HasOwner: 0
SupportAutoCommandTarget: 1
TrackInterpolationDelay: 0
GhostGroup: 0
UsePreSerialization: 0
UseSingleBaseline: 0
RollbackPredictedSpawnedGhostState: 0
RollbackPredictionOnStructuralChanges: 1
DefaultGhostMode: 0
SupportedGhostModes: 3
OptimizationMode: 0
Importance: 1
MaxSendRate: 0
prefabId:
--- !u!114 &2544095781123180609
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 6b014797e9092694b9568c5b66d34a55, type: 3}
m_Name:
m_EditorClassIdentifier: ProjectM.Authoring::ProjectM.Authoring.EnemyAuthoring
MaxHealth: 30
HitRadius: 0.7
MoveSpeed: 3
AttackRange: 1.6
AttackDamage: 5
AttackCooldownTicks: 48
@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 84c92c40b6b720441ac3a78870c0bba4
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
-151
View File
@@ -1,151 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &3885353946372160549
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3572766465862231365}
- component: {fileID: 3909651526955663392}
- component: {fileID: 3320445911748035220}
- component: {fileID: 9053853372340598254}
- component: {fileID: 6834786618115927220}
- component: {fileID: 2544095781123180609}
m_Layer: 0
m_Name: EnemyBrute
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &3572766465862231365
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1.3, y: 1.3, z: 1.3}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!33 &3909651526955663392
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &3320445911748035220
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: 9335a56cb714259428014ed979262af7, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 1
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_MaskInteraction: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!114 &9053853372340598254
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: c16549610bfe4458aa9389201d072bb6, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.Entities.Hybrid::Unity.Entities.Hybrid.Baking.LinkedEntityGroupAuthoring
--- !u!114 &6834786618115927220
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 7c79d771cedb4794bf100ce60df5f764, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.NetCode.Authoring.Hybrid::Unity.NetCode.GhostAuthoringComponent
HasOwner: 0
SupportAutoCommandTarget: 1
TrackInterpolationDelay: 0
GhostGroup: 0
UsePreSerialization: 0
UseSingleBaseline: 0
RollbackPredictedSpawnedGhostState: 0
RollbackPredictionOnStructuralChanges: 1
DefaultGhostMode: 0
SupportedGhostModes: 3
OptimizationMode: 0
Importance: 1
MaxSendRate: 0
prefabId:
--- !u!114 &2544095781123180609
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 6b014797e9092694b9568c5b66d34a55, type: 3}
m_Name:
m_EditorClassIdentifier: ProjectM.Authoring::ProjectM.Authoring.EnemyAuthoring
MaxHealth: 80
HitRadius: 1
MoveSpeed: 2
AttackRange: 2
AttackDamage: 12
AttackCooldownTicks: 70
@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 1bc4c3736da534a42a7040fcbe9c92d5
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
-164
View File
@@ -1,164 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &3885353946372160549
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3572766465862231365}
- component: {fileID: 3909651526955663392}
- component: {fileID: 3320445911748035220}
- component: {fileID: 9053853372340598254}
- component: {fileID: 6834786618115927220}
- component: {fileID: 2544095781123180609}
- component: {fileID: 5988865165041574521}
m_Layer: 0
m_Name: EnemyCharger
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &3572766465862231365
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!33 &3909651526955663392
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &3320445911748035220
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: fc4f991205d0aa347b65d89045f80c70, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 1
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_MaskInteraction: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!114 &9053853372340598254
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: c16549610bfe4458aa9389201d072bb6, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.Entities.Hybrid::Unity.Entities.Hybrid.Baking.LinkedEntityGroupAuthoring
--- !u!114 &6834786618115927220
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 7c79d771cedb4794bf100ce60df5f764, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.NetCode.Authoring.Hybrid::Unity.NetCode.GhostAuthoringComponent
HasOwner: 0
SupportAutoCommandTarget: 1
TrackInterpolationDelay: 0
GhostGroup: 0
UsePreSerialization: 0
UseSingleBaseline: 0
RollbackPredictedSpawnedGhostState: 0
RollbackPredictionOnStructuralChanges: 1
DefaultGhostMode: 0
SupportedGhostModes: 3
OptimizationMode: 0
Importance: 1
MaxSendRate: 0
prefabId:
--- !u!114 &2544095781123180609
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 6b014797e9092694b9568c5b66d34a55, type: 3}
m_Name:
m_EditorClassIdentifier: ProjectM.Authoring::ProjectM.Authoring.EnemyAuthoring
MaxHealth: 45
HitRadius: 0.8
MoveSpeed: 2.6
AttackRange: 1.7
AttackDamage: 14
AttackCooldownTicks: 48
--- !u!114 &5988865165041574521
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 9565191e0ea7fc94db934ae91a43a4cf, type: 3}
m_Name:
m_EditorClassIdentifier: ProjectM.Authoring::ProjectM.Authoring.ChargerAuthoring
@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 6da6b938e0fd50f48a885f6f50ac9d61
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 0d42b4a8ef76489458ee1ecf51b4dbca
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 31d233e9e507acf45a411f8ab0997bed
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: f29273bdfc85a8b49a4e16719e9a2568
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
-151
View File
@@ -1,151 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &3885353946372160549
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3572766465862231365}
- component: {fileID: 3909651526955663392}
- component: {fileID: 3320445911748035220}
- component: {fileID: 9053853372340598254}
- component: {fileID: 6834786618115927220}
- component: {fileID: 2544095781123180609}
m_Layer: 0
m_Name: EnemySwarmer
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &3572766465862231365
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 0.6, y: 0.6, z: 0.6}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!33 &3909651526955663392
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &3320445911748035220
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: 41d8ba155b460ab4eacca90ee2ad64cd, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 1
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_MaskInteraction: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!114 &9053853372340598254
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: c16549610bfe4458aa9389201d072bb6, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.Entities.Hybrid::Unity.Entities.Hybrid.Baking.LinkedEntityGroupAuthoring
--- !u!114 &6834786618115927220
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 7c79d771cedb4794bf100ce60df5f764, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.NetCode.Authoring.Hybrid::Unity.NetCode.GhostAuthoringComponent
HasOwner: 0
SupportAutoCommandTarget: 1
TrackInterpolationDelay: 0
GhostGroup: 0
UsePreSerialization: 0
UseSingleBaseline: 0
RollbackPredictedSpawnedGhostState: 0
RollbackPredictionOnStructuralChanges: 1
DefaultGhostMode: 0
SupportedGhostModes: 3
OptimizationMode: 0
Importance: 1
MaxSendRate: 0
prefabId:
--- !u!114 &2544095781123180609
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 6b014797e9092694b9568c5b66d34a55, type: 3}
m_Name:
m_EditorClassIdentifier: ProjectM.Authoring::ProjectM.Authoring.EnemyAuthoring
MaxHealth: 15
HitRadius: 0.5
MoveSpeed: 5
AttackRange: 1.4
AttackDamage: 3
AttackCooldownTicks: 40
@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 9855c5a5d578bb74ba2fb0d41a73a3b9
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: b0921d4cb0eedd349a257759a157653a
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: a6c2004a3cc32cc44b1bb7a795f86519
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: f77a36036567c814496e6c59c42b2082
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
-151
View File
@@ -1,151 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &3885353946372160549
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3572766465862231365}
- component: {fileID: 3909651526955663392}
- component: {fileID: 3320445911748035220}
- component: {fileID: 9053853372340598254}
- component: {fileID: 6834786618115927220}
- component: {fileID: 320054987393778360}
m_Layer: 0
m_Name: Fabricator
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &3572766465862231365
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!33 &3909651526955663392
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Mesh: {fileID: -1810898345513765494, guid: c8c42eb35b8e2e649b185de6fd33e2e7, type: 3}
--- !u!23 &3320445911748035220
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: 4cee7a5983b187943adc589b9cb1f084, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 1
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_MaskInteraction: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!114 &9053853372340598254
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: c16549610bfe4458aa9389201d072bb6, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.Entities.Hybrid::Unity.Entities.Hybrid.Baking.LinkedEntityGroupAuthoring
--- !u!114 &6834786618115927220
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 7c79d771cedb4794bf100ce60df5f764, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.NetCode.Authoring.Hybrid::Unity.NetCode.GhostAuthoringComponent
HasOwner: 0
SupportAutoCommandTarget: 1
TrackInterpolationDelay: 0
GhostGroup: 0
UsePreSerialization: 0
UseSingleBaseline: 0
RollbackPredictedSpawnedGhostState: 0
RollbackPredictionOnStructuralChanges: 1
DefaultGhostMode: 0
SupportedGhostModes: 3
OptimizationMode: 0
Importance: 1
MaxSendRate: 0
prefabId:
--- !u!114 &320054987393778360
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 014833c467fb1d6499f437b7bf76db80, type: 3}
m_Name:
m_EditorClassIdentifier: ProjectM.Authoring::ProjectM.Authoring.FabricatorAuthoring
InResourceId: 2
InAmount: 1
OutResourceId: 4
OutAmount: 3
PeriodTicks: 30
InputFromLedger: 1
@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 8dd9baab4cbf6c04f9320ed5ed764c65
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
-148
View File
@@ -1,148 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &3885353946372160549
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3572766465862231365}
- component: {fileID: 3909651526955663392}
- component: {fileID: 3320445911748035220}
- component: {fileID: 9053853372340598254}
- component: {fileID: 6834786618115927220}
- component: {fileID: 1647940186412478499}
m_Layer: 0
m_Name: Harvester
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &3572766465862231365
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 2.5, y: 2.5, z: 2.5}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!33 &3909651526955663392
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Mesh: {fileID: 4300000, guid: abc00000000010690097314383055197, type: 3}
--- !u!23 &3320445911748035220
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: c01dbe8a4e818c5469eceae8b286bf4d, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 1
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_MaskInteraction: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!114 &9053853372340598254
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: c16549610bfe4458aa9389201d072bb6, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.Entities.Hybrid::Unity.Entities.Hybrid.Baking.LinkedEntityGroupAuthoring
--- !u!114 &6834786618115927220
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 7c79d771cedb4794bf100ce60df5f764, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.NetCode.Authoring.Hybrid::Unity.NetCode.GhostAuthoringComponent
HasOwner: 0
SupportAutoCommandTarget: 1
TrackInterpolationDelay: 0
GhostGroup: 0
UsePreSerialization: 0
UseSingleBaseline: 0
RollbackPredictedSpawnedGhostState: 0
RollbackPredictionOnStructuralChanges: 1
DefaultGhostMode: 0
SupportedGhostModes: 3
OptimizationMode: 0
Importance: 1
MaxSendRate: 0
prefabId:
--- !u!114 &1647940186412478499
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: d69d7296747332b4fbe7901ec5210149, type: 3}
m_Name:
m_EditorClassIdentifier: ProjectM.Authoring::ProjectM.Authoring.HarvesterAuthoring
OutputResourceId: 2
Yield: 1
PeriodTicks: 60
@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 9e227d9b387139340933dcf55d1c3a87
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
-204
View File
@@ -1,204 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &2057690259992313649
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 4449724123933675757}
- component: {fileID: 3818890329194429404}
- component: {fileID: 6553709043537316589}
m_Layer: 0
m_Name: Model
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &4449724123933675757
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2057690259992313649}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 3572766465862231365}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!33 &3818890329194429404
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2057690259992313649}
m_Mesh: {fileID: -8131118695690612189, guid: 472a8342ece31c14f9d53ff7119b7857, type: 3}
--- !u!23 &6553709043537316589
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2057690259992313649}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: 4cee7a5983b187943adc589b9cb1f084, type: 2}
- {fileID: 2100000, guid: e600b338198602a449203a6e59c0e794, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_MaskInteraction: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!1 &3885353946372160549
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3572766465862231365}
- component: {fileID: 9053853372340598254}
- component: {fileID: 6834786618115927220}
- component: {fileID: 1794795016809289889}
- component: {fileID: 9049467567705961987}
m_Layer: 9
m_Name: Turret
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &3572766465862231365
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 4449724123933675757}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &9053853372340598254
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: c16549610bfe4458aa9389201d072bb6, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.Entities.Hybrid::Unity.Entities.Hybrid.Baking.LinkedEntityGroupAuthoring
--- !u!114 &6834786618115927220
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 7c79d771cedb4794bf100ce60df5f764, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.NetCode.Authoring.Hybrid::Unity.NetCode.GhostAuthoringComponent
HasOwner: 0
SupportAutoCommandTarget: 1
TrackInterpolationDelay: 0
GhostGroup: 0
UsePreSerialization: 0
UseSingleBaseline: 0
RollbackPredictedSpawnedGhostState: 0
RollbackPredictionOnStructuralChanges: 1
DefaultGhostMode: 0
SupportedGhostModes: 3
OptimizationMode: 0
Importance: 1
MaxSendRate: 0
prefabId:
--- !u!114 &1794795016809289889
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: dfb1e0820cbf41b4bbbe99066dfadd40, type: 3}
m_Name:
m_EditorClassIdentifier: ProjectM.Authoring::ProjectM.Authoring.TurretAuthoring
Range: 10
CooldownTicks: 30
Damage: 12
MaxHp: 120
--- !u!65 &9049467567705961987
BoxCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3885353946372160549}
m_Material: {fileID: 0}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_LayerOverridePriority: 0
m_IsTrigger: 0
m_ProvidesContacts: 0
m_Enabled: 1
serializedVersion: 3
m_Size: {x: 0.8, y: 1.2, z: 0.8}
m_Center: {x: 0, y: 0.6, z: 0}
@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 5459c9edea89bd94fa6f5043ae00eb40
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,200 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &-4441572633185463556
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 97c23e3b12dc18c42a140437e53d3951, type: 3}
m_Name: Tonemapping
m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.Tonemapping
active: 1
mode:
m_OverrideState: 1
m_Value: 1
neutralHDRRangeReductionMode:
m_OverrideState: 0
m_Value: 2
acesPreset:
m_OverrideState: 0
m_Value: 3
hueShiftAmount:
m_OverrideState: 0
m_Value: 0
detectPaperWhite:
m_OverrideState: 0
m_Value: 0
paperWhite:
m_OverrideState: 0
m_Value: 300
detectBrightnessLimits:
m_OverrideState: 0
m_Value: 1
minNits:
m_OverrideState: 0
m_Value: 0.005
maxNits:
m_OverrideState: 0
m_Value: 1000
--- !u!114 &-3245916173682126434
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 0b2db86121404754db890f4c8dfe81b2, type: 3}
m_Name: Bloom
m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.Bloom
active: 1
skipIterations:
m_OverrideState: 0
m_Value: 1
threshold:
m_OverrideState: 1
m_Value: 1.2
intensity:
m_OverrideState: 1
m_Value: 0.5
scatter:
m_OverrideState: 1
m_Value: 0.65
clamp:
m_OverrideState: 1
m_Value: 10
tint:
m_OverrideState: 1
m_Value: {r: 0.85, g: 0.92, b: 1, a: 1}
highQualityFiltering:
m_OverrideState: 1
m_Value: 1
filter:
m_OverrideState: 0
m_Value: 0
downscale:
m_OverrideState: 0
m_Value: 0
maxIterations:
m_OverrideState: 0
m_Value: 6
dirtTexture:
m_OverrideState: 0
m_Value: {fileID: 0}
dimension: 1
dirtIntensity:
m_OverrideState: 0
m_Value: 0
--- !u!114 &-2801731409977683993
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 899c54efeace73346a0a16faa3afe726, type: 3}
m_Name: Vignette
m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.Vignette
active: 1
color:
m_OverrideState: 1
m_Value: {r: 0, g: 0, b: 0, a: 1}
center:
m_OverrideState: 0
m_Value: {x: 0.5, y: 0.5}
intensity:
m_OverrideState: 1
m_Value: 0.2
smoothness:
m_OverrideState: 1
m_Value: 0.4
rounded:
m_OverrideState: 0
m_Value: 0
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: d7fd9488000d3734a9e00ee676215985, type: 3}
m_Name: PostFX_Daylight
m_EditorClassIdentifier: Unity.RenderPipelines.Core.Runtime::UnityEngine.Rendering.VolumeProfile
components:
- {fileID: -3245916173682126434}
- {fileID: -4441572633185463556}
- {fileID: 5577075502713826208}
- {fileID: -2801731409977683993}
- {fileID: 7928249266562162677}
--- !u!114 &5577075502713826208
MonoBehaviour:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 66f335fb1ffd8684294ad653bf1c7564, type: 3}
m_Name: ColorAdjustments
m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.ColorAdjustments
active: 1
postExposure:
m_OverrideState: 1
m_Value: 0.85
contrast:
m_OverrideState: 1
m_Value: 4
colorFilter:
m_OverrideState: 1
m_Value: {r: 0.93, g: 0.96, b: 1, a: 1}
hueShift:
m_OverrideState: 1
m_Value: 0
saturation:
m_OverrideState: 1
m_Value: 4
--- !u!114 &7928249266562162677
MonoBehaviour:
m_ObjectHideFlags: 3
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 558a8e2b6826cf840aae193990ba9f2e, type: 3}
m_Name: ShadowsMidtonesHighlights
m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.ShadowsMidtonesHighlights
active: 1
shadows:
m_OverrideState: 1
m_Value: {x: 0.92, y: 1, z: 1.05, w: -0.02}
midtones:
m_OverrideState: 1
m_Value: {x: 0.98, y: 1, z: 1.02, w: 0}
highlights:
m_OverrideState: 1
m_Value: {x: 1.06, y: 1, z: 0.92, w: 0}
shadowsStart:
m_OverrideState: 1
m_Value: 0
shadowsEnd:
m_OverrideState: 1
m_Value: 0.3
highlightsStart:
m_OverrideState: 1
m_Value: 0.55
highlightsEnd:
m_OverrideState: 1
m_Value: 1
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 09ca16b0a83161346aaa963465d1ec16
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
@@ -61,7 +61,7 @@ MonoBehaviour:
m_Value: 1
intensity:
m_OverrideState: 1
m_Value: 0.55
m_Value: 0.6
scatter:
m_OverrideState: 1
m_Value: 0.65
@@ -70,7 +70,7 @@ MonoBehaviour:
m_Value: 65472
tint:
m_OverrideState: 1
m_Value: {r: 0.8, g: 0.9, b: 1, a: 1}
m_Value: {r: 0.75, g: 0.95, b: 1, a: 1}
highQualityFiltering:
m_OverrideState: 1
m_Value: 1
@@ -105,13 +105,13 @@ MonoBehaviour:
active: 1
color:
m_OverrideState: 1
m_Value: {r: 0, g: 0, b: 0, a: 1}
m_Value: {r: 0, g: 0.02, b: 0.04, a: 1}
center:
m_OverrideState: 0
m_Value: {x: 0.5, y: 0.5}
intensity:
m_OverrideState: 1
m_Value: 0.32
m_Value: 0.28
smoothness:
m_OverrideState: 1
m_Value: 0.4
@@ -128,7 +128,7 @@ MonoBehaviour:
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: d7fd9488000d3734a9e00ee676215985, type: 3}
m_Name: PostFX_DarkSciFi
m_Name: PostFX_Lantern
m_EditorClassIdentifier: Unity.RenderPipelines.Core.Runtime::UnityEngine.Rendering.VolumeProfile
components:
- {fileID: -3245916173682126434}
@@ -150,16 +150,16 @@ MonoBehaviour:
active: 1
postExposure:
m_OverrideState: 1
m_Value: 0.25
m_Value: 0.35
contrast:
m_OverrideState: 1
m_Value: 12
m_Value: 10
colorFilter:
m_OverrideState: 1
m_Value: {r: 0.93, g: 0.96, b: 1, a: 1}
m_Value: {r: 0.88, g: 0.97, b: 1, a: 1}
hueShift:
m_OverrideState: 0
m_Value: 0
saturation:
m_OverrideState: 1
m_Value: 8
m_Value: 6
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 669d41422347ed94785dd2515084828f
guid: 96601eff473f9834cb459b40d0bbae14
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
@@ -1,38 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Sky_DaytimeProcedural
m_Shader: {fileID: 106, guid: 0000000000000000f000000000000000, type: 0}
m_Parent: {fileID: 0}
m_ModifiedSerializedProperties: 0
m_ValidKeywords:
- _SUNDISK_HIGH_QUALITY
m_InvalidKeywords: []
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: -1
stringTagMap: {}
disabledShaderPasses: []
m_LockedProperties:
m_SavedProperties:
serializedVersion: 3
m_TexEnvs: []
m_Ints: []
m_Floats:
- _AtmosphereThickness: 1
- _Exposure: 1.1
- _SunDisk: 2
- _SunSize: 0.04
- _SunSizeConvergence: 5
m_Colors:
- _GroundColor: {r: 0.6039216, g: 0.54901963, b: 0.4392157, a: 1}
- _SkyTint: {r: 0.65882355, g: 0.78431374, b: 0.9098039, a: 1}
m_BuildTextureStacks: []
m_AllowLocking: 1
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: e1cd63d2feef80f45a168f477934722f
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 2100000
userData:
assetBundleName:
assetBundleVariant:
@@ -1,35 +0,0 @@
using ProjectM.Simulation;
using Unity.Entities;
using UnityEngine;
namespace ProjectM.Authoring
{
/// <summary>
/// Authoring for a Conveyor belt ghost prefab. Bakes <see cref="PlacedStructure"/>{Type=Conveyor} +
/// <see cref="Conveyor"/> (default facing; BuildPlaceSystem overrides Direction per placement from the RPC) +
/// a DISABLED <see cref="ConveyorItem"/> (an empty belt). BuildPlaceSystem stamps the Cell; the transport
/// system initializes the period gate on first encounter.
/// </summary>
public class ConveyorAuthoring : MonoBehaviour
{
[Tooltip("Default belt facing (0=+X, 1=-X, 2=+Z, 3=-Z); the build RPC overrides this per placement.")]
public byte Direction = 0;
[Min(1)] public int PeriodTicks = 20;
private class ConveyorBaker : Baker<ConveyorAuthoring>
{
public override void Bake(ConveyorAuthoring authoring)
{
var entity = GetEntity(authoring, TransformUsageFlags.Dynamic);
this.AddPlacedStructure(entity, StructureType.Conveyor);
AddComponent(entity, new Conveyor
{
Direction = authoring.Direction,
PeriodTicks = authoring.PeriodTicks,
});
AddComponent(entity, new ConveyorItem { ResourceId = 0, Count = 0 });
SetComponentEnabled<ConveyorItem>(entity, false); // baked empty (disabled)
}
}
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 23131e6166dce204582bbedc8511658e
@@ -1,53 +0,0 @@
using ProjectM.Simulation;
using Unity.Entities;
using UnityEngine;
namespace ProjectM.Authoring
{
/// <summary>
/// Authoring for a Fabricator machine ghost prefab. Bakes <see cref="PlacedStructure"/>{Type=Fabricator} +
/// <see cref="Fabricator"/> recipe + a (kept) empty <see cref="MachineInput"/> buffer — the production query
/// needs the buffer as a column, but a ledger-fed Fabricator ignores it. It deposits its output directly into
/// the GLOBAL ledger, so it needs no output buffer. Default recipe (EB-2): 1 Ore -> 3 Charge, ledger-fed
/// (<see cref="Fabricator.InputFromLedger"/> != 0) — mints the turret-ammo Charge that <c>TurretFireSystem</c>
/// spends per shot.
/// </summary>
public class FabricatorAuthoring : MonoBehaviour
{
[Tooltip("Input resource id consumed per run (1=Aether, 2=Ore, 3=Biomass).")]
public byte InResourceId = ResourceId.Ore;
[Min(1)] public int InAmount = 1;
[Tooltip("Output resource id deposited to the global ledger (4 = EB-2 Charge / turret ammo).")]
public byte OutResourceId = ResourceId.Charge;
[Min(1)] public int OutAmount = 3;
[Min(1)] public int PeriodTicks = 30;
[Tooltip("EB-2: 1 = ledger-fed (consume the input from the shared ledger; no conveyor). 0 = M7 MachineInput chain.")]
public byte InputFromLedger = 1;
[Min(1f)] public float MaxHp = 120f;
private class FabricatorBaker : Baker<FabricatorAuthoring>
{
public override void Bake(FabricatorAuthoring authoring)
{
var entity = GetEntity(authoring, TransformUsageFlags.Dynamic);
this.AddPlacedStructure(entity, StructureType.Fabricator);
AddComponent(entity, new Fabricator
{
InResourceId = authoring.InResourceId,
InAmount = authoring.InAmount,
OutResourceId = authoring.OutResourceId,
OutAmount = authoring.OutAmount,
PeriodTicks = authoring.PeriodTicks,
InputFromLedger = authoring.InputFromLedger,
});
AddBuffer<MachineInput>(entity);
// EB-1 parity (DR-032): machines can die. Without Health this baker left the Fabricator OUT of
// the EnemyAISystem fortress-aggro snapshot entirely (its query keys on Health) — an invulnerable
// machine, diverging from DR-032. Now that it IS targetable, the DamageEvent buffer must exist
// (an AI strike appends into it; absent = ECB-playback throw) + Destructible lets
// HealthApplyDamageSystem destroy it at 0 (occupancy auto-frees).
this.AddDamageable(entity, authoring.MaxHp);
}
}
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 014833c467fb1d6499f437b7bf76db80
@@ -1,36 +0,0 @@
using ProjectM.Simulation;
using Unity.Entities;
using UnityEngine;
namespace ProjectM.Authoring
{
/// <summary>
/// Authoring for a Harvester machine ghost prefab (duplicate a structure ghost so the ownerless interpolated
/// GhostAuthoringComponent comes free). Bakes <see cref="PlacedStructure"/>{Type=Harvester} + <see cref="Harvester"/>
/// stats + an empty <see cref="MachineOutput"/> buffer. BuildPlaceSystem stamps the Cell at placement; the
/// production system initializes the tick baseline on first encounter (NextTick/LastProcessedTick baked 0).
/// </summary>
public class HarvesterAuthoring : MonoBehaviour
{
[Tooltip("Resource id this generator produces (1=Aether, 2=Ore, 3=Biomass).")]
public byte OutputResourceId = ResourceId.Ore;
[Min(1)] public int Yield = 1;
[Min(1)] public int PeriodTicks = 60;
private class HarvesterBaker : Baker<HarvesterAuthoring>
{
public override void Bake(HarvesterAuthoring authoring)
{
var entity = GetEntity(authoring, TransformUsageFlags.Dynamic);
this.AddPlacedStructure(entity, StructureType.Harvester);
AddComponent(entity, new Harvester
{
ResourceId = authoring.OutputResourceId,
Yield = authoring.Yield,
PeriodTicks = authoring.PeriodTicks,
});
AddBuffer<MachineOutput>(entity);
}
}
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: d69d7296747332b4fbe7901ec5210149
@@ -6,21 +6,13 @@ using UnityEngine.Serialization;
namespace ProjectM.Authoring
{
/// <summary>
/// Authoring for the baked <see cref="StructureCatalog"/> singleton (the build cost/prefab table). For the
/// M6 foundation there is one buildable type (Turret), so the entry is flat fields — only the prefab
/// object-ref + cost amount need inspector wiring; the type + cost-resource are byte consts in the baker
/// (enum-via-MCP is unreliable, and bytes dodge the cross-assembly enum-in-Burst hazard). M7 generalizes to
/// an array; the runtime <see cref="StructureCatalogEntry"/> buffer is already the data-driven shape. Place
/// once in the gameplay subscene.
/// Authoring for the baked <see cref="StructureCatalog"/> singleton (the build cost/prefab table). Flat
/// prefab + cost fields per buildable type (the type + cost-resource are byte consts in the baker —
/// enum-via-MCP is unreliable, and bytes dodge the cross-assembly enum-in-Burst hazard); the runtime
/// <see cref="StructureCatalogEntry"/> buffer is the data-driven shape. Place once in the gameplay subscene.
/// </summary>
public class StructureCatalogAuthoring : MonoBehaviour
{
[Tooltip("Turret structure ghost prefab (TurretAuthoring + GhostAuthoring).")]
public GameObject TurretPrefab;
[Tooltip("Ore cost to build a turret.")]
[Min(0)] public int TurretCostOre = 40; // DR-042 combat pass: was 10 (~1/3 node) -> 40 (~1.3 nodes); a real investment
[Tooltip("Wall structure ghost prefab (StructureAuthoring{Wall} + GhostAuthoring).")]
public GameObject WallPrefab;
@@ -33,18 +25,6 @@ namespace ProjectM.Authoring
[Tooltip("Ore cost to build a pylon.")]
[Min(0)] public int PylonCostOre = 2;
[Tooltip("Harvester machine ghost prefab (HarvesterAuthoring + GhostAuthoring).")]
public GameObject HarvesterPrefab;
[Min(0)] public int HarvesterCostOre = 20;
[Tooltip("Fabricator machine ghost prefab (FabricatorAuthoring + GhostAuthoring).")]
public GameObject FabricatorPrefab;
[Min(0)] public int FabricatorCostOre = 30;
[Tooltip("Conveyor belt ghost prefab (ConveyorAuthoring + GhostAuthoring).")]
public GameObject ConveyorPrefab;
[Min(0)] public int ConveyorCostOre = 2;
private class StructureCatalogBaker : Baker<StructureCatalogAuthoring>
{
public override void Bake(StructureCatalogAuthoring authoring)
@@ -53,16 +33,6 @@ namespace ProjectM.Authoring
AddComponent<StructureCatalog>(entity);
var buf = AddBuffer<StructureCatalogEntry>(entity);
if (authoring.TurretPrefab != null)
{
buf.Add(new StructureCatalogEntry
{
Type = StructureType.Turret,
Prefab = GetEntity(authoring.TurretPrefab, TransformUsageFlags.Dynamic),
CostResourceId = ResourceId.Ore,
CostAmount = authoring.TurretCostOre,
});
}
if (authoring.WallPrefab != null)
{
buf.Add(new StructureCatalogEntry
@@ -84,40 +54,6 @@ namespace ProjectM.Authoring
CostAmount = authoring.PylonCostOre,
});
}
if (authoring.HarvesterPrefab != null)
{
buf.Add(new StructureCatalogEntry
{
Type = StructureType.Harvester,
Prefab = GetEntity(authoring.HarvesterPrefab, TransformUsageFlags.Dynamic),
CostResourceId = ResourceId.Ore,
CostAmount = authoring.HarvesterCostOre,
});
}
if (authoring.FabricatorPrefab != null)
{
buf.Add(new StructureCatalogEntry
{
Type = StructureType.Fabricator,
Prefab = GetEntity(authoring.FabricatorPrefab, TransformUsageFlags.Dynamic),
CostResourceId = ResourceId.Ore,
CostAmount = authoring.FabricatorCostOre,
});
}
if (authoring.ConveyorPrefab != null)
{
buf.Add(new StructureCatalogEntry
{
Type = StructureType.Conveyor,
Prefab = GetEntity(authoring.ConveyorPrefab, TransformUsageFlags.Dynamic),
CostResourceId = ResourceId.Ore,
CostAmount = authoring.ConveyorCostOre,
});
}
}
}
}
@@ -1,39 +0,0 @@
using ProjectM.Simulation;
using Unity.Entities;
using UnityEngine;
namespace ProjectM.Authoring
{
/// <summary>
/// Authoring for the turret structure ghost prefab (duplicate UpgradePickup.prefab so the ownerless
/// interpolated GhostAuthoringComponent comes free). Bakes <see cref="PlacedStructure"/>{Type=Turret} +
/// <see cref="Turret"/> stats. BuildPlaceSystem stamps Cell + LastProcessedTick at placement.
/// </summary>
public class TurretAuthoring : MonoBehaviour
{
[Min(1f)] public float Range = 10f;
[Min(1)] public int CooldownTicks = 30;
[Min(1f)] public float Damage = 12f;
[Min(1f)] public float MaxHp = 120f;
private class TurretBaker : Baker<TurretAuthoring>
{
public override void Bake(TurretAuthoring authoring)
{
var entity = GetEntity(authoring, TransformUsageFlags.Dynamic);
this.AddPlacedStructure(entity, StructureType.Turret);
AddComponent(entity, new Turret
{
Range = authoring.Range,
CooldownTicks = authoring.CooldownTicks,
Damage = authoring.Damage,
});
// EB-1: structures are damageable + destructible (Husks push for them; HealthApplyDamageSystem
// destroys a Destructible at Health<=0). The DamageEvent buffer MUST exist on the archetype or an
// AI/turret strike crashes at ECB playback. NO HitRadius on purpose -> ProjectileDamageSystem (needs
// Health+HitRadius) ignores structures, so player shots never friendly-fire your own turret.
this.AddDamageable(entity, authoring.MaxHp);
}
}
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: dfb1e0820cbf41b4bbbe99066dfadd40
@@ -19,7 +19,7 @@ namespace ProjectM.Authoring
[Tooltip("All ability definitions available in the game. Indexed at runtime by AbilityId.")]
public List<AbilityDefinition> Abilities = new List<AbilityDefinition>();
[Tooltip("All character-stats definitions. Indexed at runtime by CharacterId.")]
[Tooltip("All character-stats definitions. Indexed at runtime by FrameKind.")]
public List<CharacterStatsDefinition> Characters = new List<CharacterStatsDefinition>();
private class DatabaseBaker : Baker<AbilityDatabaseAuthoring>
@@ -11,7 +11,7 @@ namespace ProjectM.Authoring
[CreateAssetMenu(menuName = "Project M/Character Stats Definition", fileName = "Character_")]
public class CharacterStatsDefinition : ScriptableObject
{
public CharacterId Id = CharacterId.Default;
public FrameKind Id = FrameKind.Default;
public string DisplayName = "Character";
[Min(0f)] public float MoveSpeed = 6f;
@@ -41,7 +41,6 @@ namespace ProjectM.Authoring
Tier = def.Tier,
StackMax = def.StackMax,
EquipSlot = def.EquipSlot,
GrantedAbilityId = def.GrantedAbilityId,
Mod0 = ModAt(def, 0),
Mod1 = ModAt(def, 1),
Mod2 = ModAt(def, 2),
@@ -33,9 +33,6 @@ namespace ProjectM.Authoring
[Tooltip("EquipSlotId byte: 0=Weapon, 1=Armor, 2=Trinket, 3=Tool, 255=not equippable.")]
public byte EquipSlot = 255;
[Tooltip("AbilityId granted when equipped in the Weapon slot (0=none): 1=Primary, 2=FastLight, 3=SlowHeavy.")]
public byte GrantedAbilityId = 0;
[Tooltip("Stat modifiers granted while equipped (first 4 used).")]
public List<ItemModAuthoring> Mods = new List<ItemModAuthoring>();
}
@@ -19,9 +19,6 @@ namespace ProjectM.Authoring
[Tooltip("Character-stats definition (move speed, turn rate, max health). Single source of those values.")]
public CharacterStatsDefinition Character;
[Tooltip("Ability definition occupying the player's primary slot.")]
public AbilityDefinition PrimaryAbility;
[Header("Fallbacks (used only if a definition above is unassigned)")]
[Min(0f)] public float FallbackMaxHealth = 100f;
@@ -42,14 +39,11 @@ namespace ProjectM.Authoring
{
var entity = GetEntity(authoring, TransformUsageFlags.Dynamic);
// Re-bake when a referenced definition's serialized values change.
// Re-bake when the referenced definition's serialized values change.
if (authoring.Character != null) DependsOn(authoring.Character);
if (authoring.PrimaryAbility != null) DependsOn(authoring.PrimaryAbility);
byte characterId = authoring.Character != null
? (byte)authoring.Character.Id : (byte)CharacterId.Default;
byte abilityId = authoring.PrimaryAbility != null
? (byte)authoring.PrimaryAbility.Id : (byte)AbilityId.Primary;
? (byte)authoring.Character.Id : (byte)FrameKind.Default;
float maxHealth = authoring.Character != null
? authoring.Character.MaxHealth : authoring.FallbackMaxHealth;
@@ -57,14 +51,11 @@ namespace ProjectM.Authoring
AddComponent<PlayerFacing>(entity);
AddComponent<PlayerInput>(entity);
// Data-driven stat refs (replace M2's inlined PlayerMoveStats / AbilityStats values).
// Data-driven stat ref (replaces M2's inlined PlayerMoveStats values); the ability model is the
// 4-socket kit below (the legacy AbilityRef/DefaultAbility/EffectiveAbilityStats bakes are deleted).
AddComponent(entity, new CharacterStatsRef { Id = characterId });
AddComponent(entity, new AbilityRef { Id = abilityId });
// Unarmed/base ability restored on weapon-unequip (AbilityRef.Id mutates when a weapon is equipped).
AddComponent(entity, new DefaultAbility { Id = abilityId });
// Effective stats: zeroed at bake, recomputed every predicted tick by StatRecomputeSystem.
AddComponent(entity, new EffectiveAbilityStats());
AddComponent(entity, new EffectiveCharacterStats());
// Empty replicated modifier stack (grown by upgrades/pickups/debug hook, server-authoritative).
@@ -82,7 +73,6 @@ namespace ProjectM.Authoring
// damageable hit radius, predicted cooldown state, and the per-tick damage inbox.
AddComponent(entity, new Health { Current = maxHealth, Max = maxHealth });
AddComponent(entity, new HitRadius { Value = authoring.HitRadius });
AddComponent<AbilityCooldown>(entity);
AddBuffer<DamageEvent>(entity);
// MC-1 dash: predicted dash window (derived from PlayerInput.Dash) + cooldown gate, baked idle/ready.
AddComponent<DashState>(entity);
@@ -110,17 +100,17 @@ namespace ProjectM.Authoring
// the Returning edge) + the server-only Blade-Dash per-dash dedup accumulator (non-replicated).
AddComponent<BoonEffects>(entity);
AddComponent<DashTrailState>(entity);
// LANTERN Phase 1 (Step 1): 4-socket kit data model - parallels AbilityRef/AbilityCooldown
// (both kept until the AbilityFireSystem migration in Step 2). AbilitySocket = cold per-socket
// loadout (EquipmentSlot-modelled, 4 empty rows); SocketCooldown = hot owner-predicted per-socket
// cooldown; FrameId = replicated frame/class signal (baked 0, written server-side at frame select).
// LANTERN Phase 1 (Step 1): 4-socket kit data model — THE ability model (the legacy single
// AbilityRef/AbilityCooldown path is deleted). AbilitySocket = cold per-socket loadout
// (EquipmentSlot-modelled, 4 empty rows; GoInGameServerSystem seeds the frame loadout at spawn);
// SocketCooldown = hot owner-predicted per-socket cooldown; FrameId = replicated frame/class signal
// (baked 0, written server-side at frame select).
var sockets = AddBuffer<AbilitySocket>(entity);
for (int sk = 0; sk < SocketId.Count; sk++)
sockets.Add(new AbilitySocket { SparkId = 0 });
AddComponent<SocketCooldown>(entity);
AddComponent<FrameId>(entity);
// Step 1b: per-socket effective-stats buffer (4 rows), folded each predicted tick by
// StatRecomputeSystem (additive; the legacy single EffectiveAbilityStats stays until steps 2/2.5).
// Step 1b: per-socket effective-stats buffer (4 rows), folded each predicted tick by StatRecomputeSystem.
var effSockets = AddBuffer<EffectiveSocketStats>(entity);
for (int sk2 = 0; sk2 < SocketId.Count; sk2++)
effSockets.Add(new EffectiveSocketStats());
@@ -5,99 +5,33 @@ using UnityEngine;
namespace ProjectM.Authoring
{
/// <summary>
/// Authoring for the GLOBAL cycle-director ghost prefab: an ownerless INTERPOLATED ghost (the
/// Authoring for the GLOBAL director ghost prefab: an ownerless INTERPOLATED ghost (the
/// GhostAuthoringComponent is inherited when this prefab is duplicated from UpgradePickup.prefab) that
/// carries the replicated macro-loop state (<see cref="CycleState"/>) and the shared resource ledger
/// (a <see cref="StorageEntry"/> buffer marked by <see cref="ResourceLedger"/>). It is GLOBAL — it must
/// carries the shared resource ledger (a <see cref="StorageEntry"/> buffer marked by
/// <see cref="ResourceLedger"/>), the replicated run-lifecycle FSM (<see cref="RunInfo"/>), the
/// expedition-objective readout, and the per-class permanent-meta tier buffer. It is GLOBAL — it must
/// carry NO <see cref="RegionTag"/> so GhostRelevancy keeps it relevant to every connection regardless of
/// region. The server CycleDirectorSpawnSystem overrides the baked CycleState at spawn (real PhaseEndTick)
/// and adds the server-only CycleRuntime.
/// region. (The old cycle/siege/goal/core state is retired — LANTERN purge.)
/// </summary>
public class CycleDirectorAuthoring : MonoBehaviour
{
[Header("Threat — post-expedition retaliation")]
[Tooltip("A completed expedition (a player returning to base) can draw a retaliation siege.")]
public bool PostExpeditionEnabled = true;
[Tooltip("Telegraph/arming delay (server ticks @60) between the return and the siege spawning.")]
public uint PostExpeditionDelayTicks = 300;
[Tooltip("Siege size floor (Husk count) for a post-expedition retaliation.")]
[Min(0)] public int SiegeSizeBase = 5;
[Tooltip("Extra Husks per unit of resources hauled back this run (0 = flat SiegeSizeBase).")]
[Min(0)] public int SiegeSizePerResource = 0;
[Tooltip("Max server ticks a siege may run before it auto-collapses (no soft-lock). 0 = no cap.")]
public uint SiegeTimeoutTicks = 3600;
[Header("Threat — scheduled base sieges (DR-042: DISABLED — reserved/inert hook)")]
[Tooltip("DR-042: OFF. A blind timed cadence was the AFK win path (auto-armed sieges the SiegeTimeout auto-cleared). The win-driver is now expedition clears; base sieges are post-expedition retaliation only. Code path kept as a config-inert reserved hook.")]
public bool ScheduleEnabled = false;
[Tooltip("Server ticks (@60) between scheduled base sieges. First fire is one interval out (mine/build grace).")]
public uint ScheduleIntervalTicks = 2700;
[Tooltip("Extra Husks per surviving wave (siege size = SiegeSizeBase + this * WaveNumber). 0 = flat.")]
[Min(0)] public int ScheduleSizePerWave = 1;
[Header("Endgame — Engine Core (END-1)")]
[Tooltip("Baked integrity ceiling of the losable Engine Core. Current is born full (or the persisted wounded value on Continue).")]
[Min(1)] public int CoreIntegrityMax = 100;
private class CycleDirectorBaker : Baker<CycleDirectorAuthoring>
{
public override void Bake(CycleDirectorAuthoring authoring)
{
var entity = GetEntity(authoring, TransformUsageFlags.Dynamic);
AddComponent(entity, new CycleState
{
Phase = CyclePhase.Calm,
CycleNumber = 1,
PhaseEndTick = 0u,
});
AddComponent<ResourceLedger>(entity);
AddBuffer<StorageEntry>(entity);
AddComponent(entity, new GoalProgress { Charge = 0, Target = 2 }); // DR-044 demo pacing: 2 expedition clears -> the climactic final siege (~10-20 min hands-off run)
// END-1: the losable Engine Core rides this GLOBAL ghost (no new ghost / no relevancy). Born full;
// CycleDirectorSpawnSystem overrides Current with a persisted wounded value on Continue.
AddComponent(entity, new CoreIntegrity
{
Current = authoring.CoreIntegrityMax,
Max = authoring.CoreIntegrityMax,
OverrunTick = 0u,
});
// END-2: the terminal run outcome is REPLICATED ([GhostField]) so the client HUD shows the win/loss
// banner by observing it. Baked here -> part of the ghost serializer (one re-bake). Born InProgress;
// CycleDirectorSpawnSystem overrides it with a persisted Victory/Loss on Continue.
AddComponent(entity, new RunOutcome { Value = RunOutcomeId.InProgress });
// DR-042 C7b: replicated expedition-objective summary (the HUD 'enemies remaining / cleared' readout).
// Born Idle; ZoneEnemyDirectorSystem is the sole writer. New [GhostField] component -> re-hashes the
// runtime-spawned director ghost (server + client bake the same prefab -> hash matches), like CoreIntegrity.
// Born Idle; RoomEnemyDirectorSystem is the sole writer.
AddComponent(entity, new ExpeditionObjective { State = ExpeditionObjectiveState.Idle, Remaining = 0 });
// Expedition redesign: the replicated run-lifecycle FSM (RunInfo, 17 [GhostField]s) + the per-class
// permanent-meta tier buffer (MetaTierState) BOTH land in this ONE coordinated re-bake (front-loaded
// ghost layout — the writer systems arrive across Steps 213 while the state sits inert/default).
// Born Staging/empty; server RunDirectorSystem / MetaSpendSystem are the sole writers.
// Expedition redesign: the replicated run-lifecycle FSM (RunInfo) + the per-class permanent-meta
// tier buffer (MetaTierState). Born Staging/empty; server RunDirectorSystem / MetaSpendSystem are
// the sole writers.
AddComponent(entity, new RunInfo { Lifecycle = RunLifecycle.Staging });
AddBuffer<MetaTierState>(entity);
AddComponent(entity, new ThreatConfig
{
PostExpeditionEnabled = (byte)(authoring.PostExpeditionEnabled ? 1 : 0),
PostExpeditionDelayTicks = authoring.PostExpeditionDelayTicks,
SizeBase = authoring.SiegeSizeBase,
SizePerExpeditionResource = authoring.SiegeSizePerResource,
StartCondition = ThreatStartCondition.Immediate,
SiegeTimeoutTicks = authoring.SiegeTimeoutTicks,
ScheduleEnabled = (byte)(authoring.ScheduleEnabled ? 1 : 0),
ScheduleIntervalTicks = authoring.ScheduleIntervalTicks,
ScheduleSizePerWave = authoring.ScheduleSizePerWave,
});
}
}
}
@@ -27,11 +27,9 @@ namespace ProjectM.Client
// Dev hotkey fallback: key -> buildable.
static readonly (UnityEngine.InputSystem.Key Key, byte Type)[] s_BuildHotkeys =
{
(UnityEngine.InputSystem.Key.B, StructureType.Turret),
(UnityEngine.InputSystem.Key.V, StructureType.Wall),
(UnityEngine.InputSystem.Key.F, StructureType.Fabricator),
// DR-042 C6d: Pylon/Harvester/Conveyor are dead (unwired automation) — dropped from the hotkey fallback
// to match the hidden build palette; their PlaceStructure execute_code statics remain for dev.
// LANTERN purge: the automation buildables (Fabricator/Harvester/Conveyor) are deleted; Pylon stays
// hidden from the hotkey fallback to match the build palette. PlaceStructure execute_code statics remain.
};
UnityEngine.Camera _camera; // cursor -> ground re-raycast for click-to-place (resolved lazily)
@@ -55,15 +53,10 @@ namespace ProjectM.Client
public static void PlaceStructure(byte type, int cellX, int cellZ, byte direction = 0) =>
s_PendingBuild.Enqueue(new PendingBuild { Type = type, CellX = cellX, CellZ = cellZ, Direction = direction });
/// <summary>EDITOR / execute_code hook: queue a turret placement at a specific cell.</summary>
public static void PlaceTurret(int cellX, int cellZ) => PlaceStructure(StructureType.Turret, cellX, cellZ);
/// <summary>EDITOR / execute_code hook: queue a wall placement at a specific cell.</summary>
public static void PlaceWall(int cellX, int cellZ) => PlaceStructure(StructureType.Wall, cellX, cellZ);
/// <summary>EDITOR / execute_code hook: queue a fabricator placement at a specific cell.</summary>
public static void PlaceFabricator(int cellX, int cellZ) => PlaceStructure(StructureType.Fabricator, cellX, cellZ);
#endif
protected override void OnCreate()
@@ -26,21 +26,18 @@ namespace ProjectM.Client
=> s_Pending.Add(new Pending { Op = op, ArgA = argA, ArgB = argB });
// Convenience wrappers (overlay buttons + execute_code).
public static void SpawnWave(int size) => Send(DebugOp.SpawnWave, size);
public static void EndSiege() => Send(DebugOp.EndSiege);
public static void SpawnWave() => Send(DebugOp.SpawnWave); // re-meant: force the next wave now
public static void StopWaves() => Send(DebugOp.EndSiege); // re-meant: quiet the arena (cull + delay waves)
public static void ClearEnemies() => Send(DebugOp.ClearEnemies);
public static void SetCalm() => Send(DebugOp.SetCalm);
public static void GrantResource(byte itemId, int count) => Send(DebugOp.GrantResource, itemId, count);
public static void GrantUpgrade() => Send(DebugOp.GrantUpgrade);
public static void Teleport(byte region) => Send(DebugOp.Teleport, region);
public static void ToggleGod() => Send(DebugOp.ToggleGod);
public static void Heal() => Send(DebugOp.Heal);
public static void Kill() => Send(DebugOp.KillPlayer);
public static void AdvanceGoal(int by) => Send(DebugOp.AdvanceGoal, by);
public static void SetHeat(int heat) => Send(DebugOp.SetHeat, heat);
/// <summary>Set the <see cref="ProjectM.Simulation.TuningKnob"/> knob to value (server-applied, x1000 fixed-point; MC-0).</summary>
public static void SetTuning(byte knob, float value) => Send(DebugOp.SetTuning, knob, Mathf.RoundToInt(value * 1000f));
/// <summary>Swap the sender's class to <paramref name="classId"/> (a <see cref="ProjectM.Simulation.CharacterId"/> byte); server-authoritative (class-switch dev tool).</summary>
/// <summary>Swap the sender's class to <paramref name="classId"/> (a <see cref="ProjectM.Simulation.FrameKind"/> byte); server-authoritative (class-switch dev tool).</summary>
public static void SetClass(byte classId) => Send(DebugOp.SetClass, classId);
public static void SetWarrior() => SetClass(ClassTraits.WarriorClass);
public static void SetRanger() => SetClass(ClassTraits.RangerClass);
@@ -15,7 +15,6 @@ namespace ProjectM.Client
public class DebugOverlay : MonoBehaviour
{
bool _open = true;
int _siegeSize = 5;
int _grantAmount = 50;
bool _tuningOpen;
Vector2 _scroll;
@@ -37,12 +36,9 @@ namespace ProjectM.Client
_scroll = GUILayout.BeginScrollView(_scroll);
GUILayout.Label("- World -");
_siegeSize = IntField("Siege size", _siegeSize);
if (GUILayout.Button("Spawn Wave / Force Siege")) DebugCommandSendSystem.SpawnWave(_siegeSize);
if (GUILayout.Button("End Siege")) DebugCommandSendSystem.EndSiege();
if (GUILayout.Button("Force Next Wave")) DebugCommandSendSystem.SpawnWave();
if (GUILayout.Button("Stop Waves")) DebugCommandSendSystem.StopWaves();
if (GUILayout.Button("Clear Enemies")) DebugCommandSendSystem.ClearEnemies();
if (GUILayout.Button("Force Calm")) DebugCommandSendSystem.SetCalm();
if (GUILayout.Button("Advance Goal +1")) DebugCommandSendSystem.AdvanceGoal(1);
GUILayout.Space(6);
GUILayout.Label("- Resources -");
@@ -112,9 +108,6 @@ namespace ProjectM.Client
TuningRow("Melee combo len", TuningKnob.MeleeComboLength, 1f, "0");
GUILayout.Space(4);
TuningRow("Struct aggro w", TuningKnob.StructureAggroWeight, 0.1f, "0.00"); // EB-1: <1 prefers structures
TuningRow("Core dmg/husk", TuningKnob.CoreDamagePerHusk, 1f, "0"); // END-1: integrity per breaching Husk
TuningRow("Core regen int", TuningKnob.CoreRegenIntervalTicks, 1f, "0"); // END-1: ticks between +1 in Calm
TuningRow("Core overrun %", TuningKnob.CoreOverrunDrainPct, 0.05f, "0.00"); // END-1: ledger fraction lost on breach
}
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 0861914135cacf948ae2adfd7f7d6870
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,26 +0,0 @@
using UnityEngine;
namespace ProjectM.Client
{
/// <summary>
/// Tiny static coordination bridge for the first-run onboarding overlay. <see cref="Active"/> is true while a
/// coach-mark step is on screen (set each frame by <see cref="OnboardingSystem"/>); <see cref="HudSystem"/>
/// reads it to suppress its own ad-hoc location/gate hint so the player ever sees a single prompt voice.
/// A presentation-layer static, so it is RESET on play-enter (the CLAUDE.md stale-static rule) to avoid a
/// stale flag surviving a fast-enter-playmode domain reload and leaving the HUD hint suppressed.
/// </summary>
public static class OnboardingState
{
/// <summary>True while the coach-mark sequence is the active prompt voice (a step is being shown).</summary>
public static bool Active;
/// <summary>True only while an EARLY base-framing step (Welcome/Move/Build/Fabricator/ReadyUp) is showing, so
/// HudSystem blanks its own location line for those (the coach-mark owns the voice) but LEAVES the room/siege/
/// out-of-ammo cues visible during the combat steps (D4). Set each frame by OnboardingSystem.</summary>
public static bool SuppressLocationLine;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
static void ResetOnPlayEnter() { Active = false; SuppressLocationLine = false; }
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 4e9b3bb074eef1c40b90591e85b90e32
@@ -1,145 +0,0 @@
using ProjectM.Simulation;
namespace ProjectM.Client
{
/// <summary>
/// Pure, engine-free logic for the first-run onboarding coach-mark sequence — the testable core of
/// <see cref="OnboardingSystem"/> (mirrors the project's <c>*Math</c> helper discipline; no UnityEngine /
/// Entities types so it unit-tests as plain C#). Defines the ordered step list, a <see cref="Snapshot"/> of the
/// observable client state each step reads, the deterministic per-step completion test, prompt copy, the
/// spatial-cue kind, and the persisted-mask helpers.
///
/// RE-AUTHORED for the expedition redesign (demo polish): base nodes are gone (mining happens IN the run),
/// so the old at-base Mine step is dead — the first lap is now Build (grubstake Ore) → Fabricator →
/// READY UP → fight rooms (mine in-run) → boon → boss/return → defend. Step BITS are re-meant, never
/// renamed-in-place semantics: a veteran's full mask stays dormant; a partial first-run mask at worst
/// replays one beat.
///
/// Pacing (operator-locked = soft-gated): a step shows until its action is performed (no per-step timeout),
/// EXCEPT two info beats — <see cref="Fabricator"/> and <see cref="Defend"/> — which also auto-advance, plus
/// the timed <see cref="Welcome"/> strip. Veteran / co-op auto-suppress falls out for free: the count-based
/// steps (<see cref="Build"/>, <see cref="Fabricator"/>) test an ABSOLUTE structure count, so a client joining
/// an already-built base satisfies them on entry and skips straight past.
/// </summary>
public static class OnboardingStepMath
{
// ---- ordered steps (byte ids; bit i of GameSettings.OnboardingMask = step i complete) ----
public const byte Welcome = 0; // tiny win-condition framing strip (timed)
public const byte Move = 1;
public const byte Build = 2; // open palette + place a Turret (the 50-Ore grubstake covers it)
public const byte Fabricator = 3; // Ore -> Charge (soft info beat)
public const byte ReadyUp = 4; // press T / click READY — the party launches together
public const byte Rooms = 5; // fight the rooms; attack crystal nodes to haul resources
public const byte Boon = 6; // clear a room -> pick 1 of 3 boons + choose the path
public const byte Return = 7; // fell the boss — the haul + the Engine charge come home
public const byte Defend = 8; // survive the retaliation siege (soft info beat)
public const byte Done = 9; // closing beat
public const byte StepCount = 10;
// ---- tunable thresholds (public so the EditMode tests pin the contract) ----
public const float WelcomeSeconds = 5f;
public const float MoveThreshold = 3f; // accumulated player movement (world units)
public const float FabricatorSoftSeconds = 14f; // soft beat auto-advance if no Fabricator built
public const float DefendNoSiegeSeconds = 20f; // advance if no siege ever materialises
public const float DoneSeconds = 6f; // closing beat lingers before going dormant
public const float RoomsSeconds = 7f; // D2: keep the mine-the-crystals prompt + node pointer up a beat AFTER teleport (past the ~3s launch countdown -> ~4s in the room)
public const float ReturnMaxSeconds = 75f; // D3: soft backstop so a missed homecoming signal can NEVER stall the sequence
// ---- spatial-cue kinds the System resolves to a live world target ----
public const byte PointerNone = 0;
public const byte PointerOreNode = 1;
public const byte PointerBaseGate = 2; // RETIRED target (walk-in gate died with the redesign); byte kept
public const byte PointerExpeditionGate = 3; // RETIRED target; byte kept for mask/step stability
/// <summary>Observable client state for one evaluation. Built by the System from ECS + input each frame.</summary>
public struct Snapshot
{
public float StepElapsed; // seconds the current step has been shown
public float MoveDistance; // accumulated player movement since the Move step began
public int TurretCount; // live Turret structures (absolute)
public int FabricatorCount; // live Fabricator structures (absolute)
public bool LocalReady; // the LOCAL player's replicated PlayerReady flag
public byte Lifecycle; // replicated RunInfo.Lifecycle (RunLifecycle.*)
public bool OnExpedition; // local player is in the expedition region
public byte ObjectiveState; // ExpeditionObjective.State (Idle/Active/Cleared)
public bool SawSiege; // a Siege phase was observed while the Defend step was showing
public bool WasOnExpedition;// D3: latched true once the player was seen on expedition during the Return step (so a start-at-base Return doesn't instantly satisfy)
public byte Phase; // CycleState.Phase (Calm/Siege)
}
/// <summary>True when the step's taught action is complete (or its soft timeout has elapsed).</summary>
public static bool IsSatisfied(byte step, in Snapshot s)
{
switch (step)
{
case Welcome: return s.StepElapsed >= WelcomeSeconds;
case Move: return s.MoveDistance >= MoveThreshold;
case Build: return s.TurretCount >= 1;
case Fabricator: return s.FabricatorCount >= 1 || s.StepElapsed >= FabricatorSoftSeconds;
case ReadyUp: return s.LocalReady || s.Lifecycle != RunLifecycle.Staging;
case Rooms: return s.OnExpedition && s.StepElapsed >= RoomsSeconds; // D2: show the mine prompt + node pointer IN the room, not the instant we teleport
case Boon: return s.ObjectiveState == ExpeditionObjectiveState.Cleared
|| s.Lifecycle == RunLifecycle.RoomReward
|| s.Lifecycle == RunLifecycle.RouteSelect;
case Return: return (s.WasOnExpedition && !s.OnExpedition) || s.StepElapsed >= ReturnMaxSeconds; // D3: home AFTER being on expedition, else a soft timeout (never gate on the 1-tick Returning edge)
case Defend: return s.SawSiege ? s.Phase == CyclePhase.Calm : s.StepElapsed >= DefendNoSiegeSeconds;
case Done: return s.StepElapsed >= DoneSeconds;
default: return true;
}
}
/// <summary>Which world target (if any) the prompt should point at this step.</summary>
public static byte PointerKind(byte step)
{
switch (step)
{
case Rooms: return PointerOreNode; // in-run crystal nodes (base nodes no longer exist)
default: return PointerNone;
}
}
/// <summary>Ultra-short, verb-first prompt copy with the player's real input glyph (scheme-aware).</summary>
public static string Prompt(byte step, bool gamepad)
{
string move = gamepad ? "Left Stick" : "WASD";
string build = gamepad ? "Y" : "Tab"; // matches the existing HUD build-discovery chip glyph
switch (step)
{
case Welcome: return "CLEAR 2 EXPEDITIONS to charge the Engine, then survive the FINAL SIEGE. (Space to continue · Esc → How to Play)";
case Move: return move + " — Move " + (gamepad ? "B" : "LShift") + " — Dash (brief invulnerability)";
case Build: return build + " — open Build, place a Turret by your Core (your " + ProjectM.Simulation.Tuning.StartingOre + " starting Ore covers it)";
case Fabricator: return "Build a Fabricator — turrets need Charge (Ore → ammo)";
case ReadyUp: return "Press T (or click READY UP) — when everyone is ready, the party launches";
case Rooms: return (gamepad ? "X attack · LT class ability" : "LMB attack · RMB class ability") + " — clear the room; shoot the glowing crystals for your haul";
case Boon: return "Clear the room — pick 1 of 3 BOONS, then choose your path on the map";
case Return: return "Fell the ALPHA HUSK, then return home — your haul + the Engine charge (+1) come with you";
case Defend: return "Defend the Core! — a completed run provokes a retaliation siege";
case Done: return "You've got it — clear 2 expeditions to fill the Engine, then hold the final siege to win.";
default: return "";
}
}
// ---- persisted-mask helpers (GameSettings.OnboardingMask) ----
/// <summary>D4: the early BASE-framing steps (the coach-mark owns the prompt voice, so the HUD blanks its own
/// location line) vs the combat steps (the HUD MUST keep showing room/siege/out-of-ammo cues). Welcome..ReadyUp
/// are early. Read each frame by OnboardingSystem to set OnboardingState.SuppressLocationLine.</summary>
public static bool IsEarlyStep(byte step) => step <= ReadyUp;
/// <summary>All steps complete (the sequence is dormant).</summary>
public static bool AllComplete(int mask)
{
int all = (1 << StepCount) - 1;
return (mask & all) == all;
}
/// <summary>The lowest not-yet-completed step (resume point); <see cref="Done"/> when all are complete.</summary>
public static byte FirstIncomplete(int mask)
{
for (byte i = 0; i < StepCount; i++)
if ((mask & (1 << i)) == 0) return i;
return Done;
}
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: c264496096436e74ebba163a7a5d2205
@@ -1,332 +0,0 @@
using ProjectM.Simulation;
using Unity.Entities;
using Unity.Mathematics;
using Unity.NetCode;
using Unity.Transforms;
using UnityEngine;
using UnityEngine.UIElements;
namespace ProjectM.Client
{
/// <summary>
/// First-run onboarding overlay — a CLIENT-ONLY, observe-only presentation <see cref="SystemBase"/> in
/// <see cref="PresentationSystemGroup"/> (same shape/constraints as <see cref="HudSystem"/>: never mutates the
/// sim, never destroys a ghost, reads already-replicated state once per frame). Owns its own runtime UIDocument
/// (sortingOrder 60 — above the HUD's 50, below the pause overlay's 100) showing a single bottom-center
/// coach-mark prompt plus a world-space directional pointer.
///
/// The sequence is PER-CLIENT and client-local: progress lives in <see cref="GameSettings.OnboardingMask"/>
/// (via <see cref="SettingsService"/>), keyed to THIS player's own first-encounter — so a veteran host sees
/// nothing, a brand-new join-client is still taught, and a save wipe never re-teaches the host (the mask is in
/// settings.json, not the host-only SaveData). Soft-gated pacing: a step shows until its action is done; the
/// pure rules + auto-suppress (absolute count checks) live in <see cref="OnboardingStepMath"/>.
/// </summary>
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
[UpdateInGroup(typeof(PresentationSystemGroup))]
public partial class OnboardingSystem : SystemBase
{
const float ExpeditionRegionXMin = RegionMath.RegionBoundaryX; // player x past this = the +1000 expedition region (mirrors HudSystem)
GameObject _go;
UIDocument _doc;
bool _built;
Label _prompt;
Label _pointer;
// step machine (in-memory; persisted to the mask on each completion)
bool _maskLoaded;
int _mask;
byte _step;
bool _stepInit;
float _stepElapsed;
float _moveAccum;
float3 _lastPos;
bool _sawSiege;
bool _wasOnExpedition; // D3: latched true once the player is seen on expedition during the Return step
protected override void OnStartRunning()
{
if (_go != null) return;
_go = new GameObject("~Onboarding");
_doc = _go.AddComponent<UIDocument>();
_doc.panelSettings = MenuUi.LoadPanelSettings();
_doc.sortingOrder = 60; // above HUD (50), below pause (100)
}
protected override void OnDestroy()
{
OnboardingState.Active = false; // never let the static outlive its owning system (HUD suppression)
if (_go != null) Object.Destroy(_go);
}
protected override void OnUpdate()
{
if (_doc == null) return;
var root = _doc.rootVisualElement;
if (root == null) return;
if (!_built) { BuildTree(root); _built = true; }
OnboardingState.SuppressLocationLine = false; // D4: default off each frame; a shown step sets it per-step below
float dt = SystemAPI.Time.DeltaTime; // wall-frame delta — correct in a presentation system
// ---- local player presence + position ----
bool havePlayer = false; float3 playerPos = default;
foreach (var lt in SystemAPI.Query<RefRO<LocalTransform>>().WithAll<GhostOwnerIsLocal, PlayerTag>())
{ havePlayer = true; playerPos = lt.ValueRO.Position; break; }
var settings = SettingsService.Current;
if (!_maskLoaded)
{
_mask = settings.OnboardingMask;
_step = OnboardingStepMath.FirstIncomplete(_mask);
_stepInit = false;
_maskLoaded = true;
}
bool hintsOn = settings.TutorialHints != 0;
// Dormant (hints off / all steps done) or no local player yet → fully hidden, no voice.
if (!hintsOn || OnboardingStepMath.AllComplete(_mask) || !havePlayer)
{
OnboardingState.Active = false;
root.style.display = DisplayStyle.None;
return;
}
// ---- remaining observable state ----
byte lifecycle = RunLifecycle.Staging;
if (SystemAPI.TryGetSingleton<RunInfo>(out var runInfo)) lifecycle = runInfo.Lifecycle;
bool localReady = false;
foreach (var pr in SystemAPI.Query<RefRO<PlayerReady>>().WithAll<GhostOwnerIsLocal, PlayerTag>())
{ localReady = pr.ValueRO.Value != 0; break; }
CountStructures(out int turrets, out int fabs);
byte phase = CyclePhase.Calm;
if (SystemAPI.TryGetSingleton<CycleState>(out var cyc)) phase = cyc.Phase;
byte objState = ExpeditionObjectiveState.Idle;
if (SystemAPI.TryGetSingleton<ExpeditionObjective>(out var obj)) objState = obj.State;
bool onExp = playerPos.x > ExpeditionRegionXMin;
// ---- per-step entry init (baselines) ----
if (!_stepInit)
{
_stepElapsed = 0f; _moveAccum = 0f; _sawSiege = false; _wasOnExpedition = false;
_lastPos = playerPos;
_stepInit = true;
}
// ---- advance (FROZEN while the pause overlay is open, so the timed beats — Welcome/Fabricator/
// Defend/Done — aren't silently lost behind the pause dim that sits above this overlay) ----
if (!PauseMenuController.Open)
{
_stepElapsed += dt;
if (_step == OnboardingStepMath.Move) _moveAccum += math.distance(playerPos, _lastPos);
if (_step == OnboardingStepMath.Defend && phase == CyclePhase.Siege) _sawSiege = true;
if (_step == OnboardingStepMath.Return && onExp) _wasOnExpedition = true; // D3: latch "was on expedition" so a start-at-base Return doesn't instantly satisfy
var snap = new OnboardingStepMath.Snapshot
{
StepElapsed = _stepElapsed,
MoveDistance = _moveAccum,
LocalReady = localReady,
Lifecycle = lifecycle,
TurretCount = turrets,
FabricatorCount = fabs,
OnExpedition = onExp,
ObjectiveState = objState,
SawSiege = _sawSiege,
WasOnExpedition = _wasOnExpedition,
Phase = phase,
};
// The two pure-message beats can be dismissed with any input EXCEPT Esc (Esc opens Pause; see
// AnyInputPressed) so following the "Esc → Pause → How to Play" hint doesn't self-skip the framing.
bool skip = (_step == OnboardingStepMath.Welcome && ConfirmPressed()) || (_step == OnboardingStepMath.Done && AnyInputPressed()); // D5: Welcome dismisses ONLY on an explicit confirm (not WASD) so the win-condition strip is actually read
if (skip || OnboardingStepMath.IsSatisfied(_step, snap))
{
_mask |= (1 << _step);
Persist(_mask);
_step = OnboardingStepMath.FirstIncomplete(_mask); // auto-suppressed steps cascade one/frame
_stepInit = false;
if (OnboardingStepMath.AllComplete(_mask))
{
OnboardingState.Active = false;
root.style.display = DisplayStyle.None;
return;
}
}
}
_lastPos = playerPos;
// ---- show the current step ----
OnboardingState.Active = true;
OnboardingState.SuppressLocationLine = OnboardingStepMath.IsEarlyStep(_step); // D4: blank the HUD line only for the early base-framing steps
root.style.display = DisplayStyle.Flex;
bool gamepad = AimPresentation.Scheme == InputSchemeId.Gamepad;
_prompt.text = OnboardingStepMath.Prompt(_step, gamepad);
UpdatePointer(_step, playerPos);
}
// ---- state gathering helpers ----
void CountStructures(out int turrets, out int fabs)
{
turrets = 0; fabs = 0;
foreach (var ps in SystemAPI.Query<RefRO<PlacedStructure>>())
{
byte t = ps.ValueRO.Type;
if (t == StructureType.Turret) turrets++;
else if (t == StructureType.Fabricator) fabs++;
}
}
void Persist(int mask)
{
var s = SettingsService.Current;
s.OnboardingMask = mask;
SettingsService.Save(s); // atomic write; ~once per completed step
}
static bool AnyInputPressed()
{
var kb = UnityEngine.InputSystem.Keyboard.current;
// any key dismisses a message beat — EXCEPT Esc, which is the pause key (don't self-skip the framing).
if (kb != null && kb.anyKey.wasPressedThisFrame && !kb.escapeKey.wasPressedThisFrame) return true;
var ms = UnityEngine.InputSystem.Mouse.current;
if (ms != null && ms.leftButton.wasPressedThisFrame) return true;
var gp = UnityEngine.InputSystem.Gamepad.current;
if (gp != null && (gp.buttonSouth.wasPressedThisFrame || gp.startButton.wasPressedThisFrame)) return true;
return false;
}
// D5: an EXPLICIT confirm only (Space/Enter/click/South) — movement keys must NOT skip the Welcome strip.
static bool ConfirmPressed()
{
var kb = UnityEngine.InputSystem.Keyboard.current;
if (kb != null && (kb.spaceKey.wasPressedThisFrame || kb.enterKey.wasPressedThisFrame)) return true;
var ms = UnityEngine.InputSystem.Mouse.current;
if (ms != null && ms.leftButton.wasPressedThisFrame) return true;
var gp = UnityEngine.InputSystem.Gamepad.current;
if (gp != null && (gp.buttonSouth.wasPressedThisFrame || gp.startButton.wasPressedThisFrame)) return true;
return false;
}
// ---- world-space pointer ----
bool ResolveTarget(byte kind, float3 playerPos, out float3 target)
{
target = default;
if (kind == OnboardingStepMath.PointerOreNode)
{
float best = float.MaxValue; bool found = false;
foreach (var lt in SystemAPI.Query<RefRO<LocalTransform>>().WithAll<ResourceNode>())
{
float d = math.distancesq(lt.ValueRO.Position, playerPos);
if (d < best) { best = d; target = lt.ValueRO.Position; found = true; }
}
return found;
}
// Step 11: the walk-in ExpeditionGate was RETIRED (the ready-check launches runs), so gate pointers
// have no world target — hide the arrow; the step text still teaches. Step 14's HUD pass re-points
// onboarding at the READY panel instead.
return false;
}
void UpdatePointer(byte step, float3 playerPos)
{
byte kind = OnboardingStepMath.PointerKind(step);
var cam = Camera.main;
var root = _doc.rootVisualElement;
if (kind == OnboardingStepMath.PointerNone || cam == null || !ResolveTarget(kind, playerPos, out float3 target))
{ _pointer.style.display = DisplayStyle.None; return; }
float pw = root.layout.width, ph = root.layout.height;
if (pw <= 1f || ph <= 1f) { _pointer.style.display = DisplayStyle.None; return; }
Vector3 sp = cam.WorldToScreenPoint((Vector3)target);
bool behind = sp.z < 0f;
float px = (sp.x / Mathf.Max(1f, Screen.width)) * pw;
float py = (1f - sp.y / Mathf.Max(1f, Screen.height)) * ph;
if (behind) { px = pw - px; py = ph - py; }
const float margin = 64f;
bool off = behind || px < margin || px > pw - margin || py < margin || py > ph - margin;
float cx = pw * 0.5f, cy = ph * 0.5f;
float dx = px - cx, dy = py - cy;
float len = Mathf.Sqrt(dx * dx + dy * dy);
if (len < 0.001f) { dx = 1f; dy = 0f; len = 1f; }
float ndx = dx / len, ndy = dy / len;
float ax, ay;
if (off)
{
// intersect the center→target ray with the margin rectangle (edge arrow)
float tx = (ndx > 0 ? (pw - margin - cx) : (margin - cx)) / (Mathf.Abs(ndx) < 1e-4f ? (ndx < 0 ? -1e-4f : 1e-4f) : ndx);
float ty = (ndy > 0 ? (ph - margin - cy) : (margin - cy)) / (Mathf.Abs(ndy) < 1e-4f ? (ndy < 0 ? -1e-4f : 1e-4f) : ndy);
float tt = Mathf.Min(Mathf.Abs(tx), Mathf.Abs(ty));
ax = cx + ndx * tt; ay = cy + ndy * tt;
}
else { ax = px; ay = py - 44f; } // float just above the on-screen target
float angle = Mathf.Atan2(dy, dx) * Mathf.Rad2Deg; // "▶" art points +x at 0°
_pointer.style.left = ax - 15f;
_pointer.style.top = ay - 18f;
_pointer.style.rotate = new StyleRotate(new Rotate(new Angle(angle)));
_pointer.style.display = DisplayStyle.Flex;
}
// ---- UITK construction ----
void BuildTree(VisualElement root)
{
root.style.position = Position.Absolute;
root.style.left = 0; root.style.right = 0; root.style.top = 0; root.style.bottom = 0;
root.pickingMode = PickingMode.Ignore; // never eat world clicks
var panel = new VisualElement();
panel.style.position = Position.Absolute;
panel.style.bottom = 210; panel.style.left = 0; panel.style.right = 0;
panel.style.flexDirection = FlexDirection.Row;
panel.style.justifyContent = Justify.Center;
panel.style.alignItems = Align.Center;
panel.pickingMode = PickingMode.Ignore;
var chip = new VisualElement();
chip.style.backgroundColor = new Color(0.05f, 0.07f, 0.10f, 0.92f);
chip.style.paddingLeft = 22; chip.style.paddingRight = 22;
chip.style.paddingTop = 10; chip.style.paddingBottom = 10;
chip.style.maxWidth = 920;
chip.pickingMode = PickingMode.Ignore;
MenuUi.Round(chip, 8);
MenuUi.Border(chip, new Color(MenuUi.Accent.r, MenuUi.Accent.g, MenuUi.Accent.b, 0.55f), 1);
_prompt = new Label(string.Empty);
_prompt.style.color = MenuUi.TextCol;
_prompt.style.fontSize = 18;
_prompt.style.unityFontStyleAndWeight = FontStyle.Bold;
_prompt.style.unityTextAlign = TextAnchor.MiddleCenter;
_prompt.style.whiteSpace = WhiteSpace.Normal;
var theme = HudTheme.Get();
if (theme != null) theme.ApplyBody(_prompt.style);
chip.Add(_prompt);
panel.Add(chip);
root.Add(panel);
_pointer = new Label("▶"); // ▶ right-pointing triangle (rotated toward the target)
_pointer.style.position = Position.Absolute;
_pointer.style.fontSize = 30;
_pointer.style.color = MenuUi.Accent;
_pointer.style.unityFontStyleAndWeight = FontStyle.Bold;
_pointer.pickingMode = PickingMode.Ignore;
_pointer.style.display = DisplayStyle.None;
root.Add(_pointer);
root.style.display = DisplayStyle.None;
}
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: b4828f5a68386fa4da379dcddbf629de
@@ -144,10 +144,8 @@ namespace ProjectM.Client
// Hide the OS cursor only while aiming AND focused; restore otherwise (focus loss / pre-spawn) so an
// unfocused editor or a windowed session is never stranded with an invisible pointer.
// END-2: while the run is over (terminal banner up) keep the cursor visible so the player can click the
// Play Again / Quit buttons, regardless of aim state. AimReticleSystem is the sole Cursor.visible writer.
bool runOver = SystemAPI.TryGetSingleton<RunOutcome>(out var ro) && ro.Value != RunOutcomeId.InProgress;
bool wantHidden = haveTarget && Application.isFocused && !AimPresentation.ForceCursorVisible && !runOver;
// AimReticleSystem is the sole Cursor.visible writer.
bool wantHidden = haveTarget && Application.isFocused && !AimPresentation.ForceCursorVisible;
if (wantHidden != _cursorHidden)
{
if (wantHidden) Cursor.lockState = CursorLockMode.None;
@@ -6,13 +6,12 @@ using UnityEngine;
namespace ProjectM.Client
{
/// <summary>
/// Client-only AMBIENT audio + cycle-phase stingers. A managed presentation <see cref="SystemBase"/>
/// (<see cref="PresentationSystemGroup"/>, main thread, no Burst) that OBSERVES the replicated
/// <see cref="CycleState"/> and never touches the simulation. On start it plays a low, seamless-looping
/// procedural drone (asset-free, <c>AudioClip.Create</c> like <c>CombatFeedbackSystem.MakeClip</c>); each
/// time the cycle phase changes it plays a short procedural stinger and eases the drone's intensity by phase
/// (calmer at base, tenser during Defend / "wave incoming"). Lives only in the client world, so the server
/// never creates audio and nothing here affects determinism. Volumes are deliberately conservative + tunable.
/// Client-only AMBIENT audio bed + run cues. A managed presentation <see cref="SystemBase"/>
/// (<see cref="PresentationSystemGroup"/>, main thread, no Burst) that plays a low, seamless-looping
/// procedural drone (asset-free, <c>AudioClip.Create</c> like <c>CombatFeedbackSystem.MakeClip</c>) plus
/// launch-countdown beeps and the boss-arrival roar — replicated-state observations only. Lives only in the
/// client world, so the server never creates audio and nothing here affects determinism. Volumes are
/// deliberately conservative. (The cycle-phase stingers + Core alarm retired with the siege loop — LANTERN purge.)
/// </summary>
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
[UpdateInGroup(typeof(PresentationSystemGroup))]
@@ -20,29 +19,17 @@ namespace ProjectM.Client
{
AudioSource _ambient;
AudioClip _ambientClip;
AudioClip _stingExpedition;
AudioClip _stingDefend;
AudioClip _stingBuild;
GameObject _root;
byte _lastPhase;
bool _phaseInit;
AudioClip _stingCoreHit;
int _lastCore = -1;
float _coreStingCooldown;
AudioClip _stingBeep, _stingRoar;
int _lastCountdownSec = -1;
bool _bossRoared;
const float AmbientBaseVolume = 0.10f; // low bed; Defend eases up to ~1.7x
const float AmbientBaseVolume = 0.10f; // low ambient bed
protected override void OnCreate()
{
_ambientClip = MakeDrone();
_stingExpedition = MakeSting(520f, 880f, 0.45f, 0.30f); // airy rising "deploy"
_stingDefend = MakeSting(300f, 140f, 0.55f, 0.42f); // tense falling "wave incoming"
_stingBuild = MakeSting(440f, 660f, 0.40f, 0.26f); // soft confirm
_stingCoreHit = MakeSting(240f, 70f, 0.30f, 0.55f); // harsh falling "Core hit" alarm
_stingBeep = MakeSting(880f, 880f, 0.09f, 0.30f); // countdown tick
_stingRoar = MakeSting(90f, 38f, 0.90f, 0.60f); // boss-arrival roar (low falling growl)
}
@@ -68,36 +55,8 @@ namespace ProjectM.Client
protected override void OnUpdate()
{
if (_ambient == null) return;
if (!SystemAPI.TryGetSingleton<CycleState>(out var cyc)) return;
byte phase = cyc.Phase;
if (!_phaseInit)
{
_lastPhase = phase; // adopt the current phase silently (no stinger on first observe)
_phaseInit = true;
}
else if (phase != _lastPhase)
{
PlaySting(phase);
_lastPhase = phase;
}
// Ease the drone intensity toward the phase target (tenser during Defend).
float target = phase == CyclePhase.Siege ? AmbientBaseVolume * 1.7f : AmbientBaseVolume;
_ambient.volume = Mathf.MoveTowards(_ambient.volume, target * GameVolume.Music, SystemAPI.Time.DeltaTime * 0.25f);
// Core-under-attack alarm: a falling sting on each CoreIntegrity drop (rate-limited) so a base
// breach is AUDIBLE even when the fight has the player's eyes elsewhere.
_coreStingCooldown -= SystemAPI.Time.DeltaTime;
if (SystemAPI.TryGetSingleton<CoreIntegrity>(out var core))
{
if (_lastCore >= 0 && core.Current < _lastCore && _coreStingCooldown <= 0f)
{
_ambient.PlayOneShot(_stingCoreHit, 0.8f * GameVolume.Sfx);
_coreStingCooldown = 0.7f;
}
_lastCore = core.Current;
}
_ambient.volume = Mathf.MoveTowards(_ambient.volume, AmbientBaseVolume * GameVolume.Music, SystemAPI.Time.DeltaTime * 0.25f);
// Launch countdown beeps (3-2-1) + the boss-arrival roar — replicated-state observations only.
if (SystemAPI.TryGetSingleton<RunInfo>(out var runAudio))
@@ -131,13 +90,6 @@ namespace ProjectM.Client
}
}
void PlaySting(byte phase)
{
AudioClip clip = phase == CyclePhase.Siege ? _stingDefend : _stingBuild;
if (clip != null && _ambient != null)
_ambient.PlayOneShot(clip, 0.6f * GameVolume.Music);
}
// ---- Procedural audio (asset-free; mirrors CombatFeedbackSystem.MakeClip) ----
// A low, seamless-looping pad: each partial completes an integer number of cycles over the buffer
@@ -176,6 +128,6 @@ namespace ProjectM.Client
}
// Short one-shot tone sweeping f0->f1 with an exponential decay envelope.
static AudioClip MakeSting(float f0, float f1, float dur, float vol) => FeedbackFx.MakeClip("sting", f0, f1, dur, vol, decay: 3.5f);
static AudioClip MakeSting(float f0, float f1, float dur, float vol) => FeedbackFx.MakeClip("sting", f0, f1, dur, vol, decay: 3.5f);
}
}
@@ -72,7 +72,7 @@ namespace ProjectM.Client
protected override void OnUpdate()
{
if (UnityEngine.SceneManagement.SceneManager.GetActiveScene().name != "Game") { Hide(); return; }
if (!ScenePolicy.IsGameplayScene()) { Hide(); return; }
if (_cam == null) _cam = Camera.main;
if (_cam == null) return;
if (!AmbientLifeConfig.Enabled) { Hide(); return; }
@@ -63,8 +63,6 @@ namespace ProjectM.Client
}
bool haveRun = SystemAPI.TryGetSingleton<RunInfo>(out var runInfo);
bool haveCycle = SystemAPI.TryGetSingleton<CycleState>(out var cyc);
bool siege = haveCycle && cyc.Phase == CyclePhase.Siege;
// Resources from the ledger (last entry per type wins, matching the core loop).
int aether = 0, ore = 0, bio = 0;
@@ -83,9 +81,9 @@ namespace ProjectM.Client
// Local class from the replicated AbilityRef (tracks the dev class-switch; PlayerClass is server-only).
byte localClass = ClassTraits.WarriorClass;
bool haveLocalPlayer = false;
foreach (var (fr, ar) in SystemAPI.Query<RefRO<FrameId>, RefRO<AbilityRef>>().WithAll<PlayerTag, GhostOwnerIsLocal>())
foreach (var fr in SystemAPI.Query<RefRO<FrameId>>().WithAll<PlayerTag, GhostOwnerIsLocal>())
{
localClass = fr.ValueRO.Value != 0 ? fr.ValueRO.Value : ClassTraits.ClassForAbility(ar.ValueRO.Id); // FrameId signal, AbilityRef fallback
localClass = ClassTraits.Normalize(fr.ValueRO.Value); // FrameId is the sole class signal (legacy AbilityRef deleted)
haveLocalPlayer = true;
break;
}
@@ -93,7 +91,7 @@ namespace ProjectM.Client
// Faithful reproduction of the original `metaShow` gate: class/prep were shown on the SAME condition as
// the meta shop, which requires the meta catalog + tier buffer to exist.
DynamicBuffer<MetaTierState> metaRecord = default;
bool metaShow = haveRun && runInfo.Lifecycle == RunLifecycle.Staging && haveLocalPlayer && !siege
bool metaShow = haveRun && runInfo.Lifecycle == RunLifecycle.Staging && haveLocalPlayer
&& SystemAPI.TryGetSingleton<MetaUpgradeCatalog>(out var metaCat) && metaCat.Value.IsCreated
&& SystemAPI.TryGetSingletonBuffer<MetaTierState>(out metaRecord, true);
@@ -1,91 +0,0 @@
using System.Collections.Generic;
using ProjectM.Simulation;
using Unity.Entities;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace ProjectM.Client
{
/// <summary>
/// The Engine Core's CRYSTAL answers its replicated <see cref="CoreIntegrity"/> (07-01 backlog: the mesh sat
/// static while draining). Client-only observe-only presentation: the cosmetic <c>CoreCrystals</c> /
/// <c>CoreMachine</c> GameObjects in Game.unity (classic URP renderers, not entities) are tinted via
/// MaterialPropertyBlock — darker + blood-shifted as integrity falls, a white-hot flash on each hit (pairs
/// with the AmbientAudioSystem alarm). Property-guarded per the shader-value rule (only materials exposing
/// _BaseColor are touched); per-renderer MPBs never mutate the shared material assets (no bleed).
/// </summary>
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
[UpdateInGroup(typeof(PresentationSystemGroup))]
public partial class CoreVisualFeedbackSystem : SystemBase
{
static readonly int BaseColorId = Shader.PropertyToID("_BaseColor");
static readonly string[] TargetNames = { "CoreCrystals", "CoreMachine" };
const float FlashSeconds = 0.35f;
readonly List<Renderer> _renderers = new();
readonly List<Color> _authoredColors = new();
MaterialPropertyBlock _mpb;
bool _resolved;
int _lastCore = -1;
float _flashLeft;
protected override void OnUpdate()
{
if (!SystemAPI.TryGetSingleton<CoreIntegrity>(out var core) || core.Max <= 0)
return;
// One-time renderer resolve, deferred until Game.unity is actually the active scene (the frontend
// path creates this world a frame BEFORE the scene loads — latching early would find nothing).
if (!_resolved)
{
var scene = SceneManager.GetActiveScene();
if (!scene.isLoaded || scene.name != "Game") return;
Resolve();
}
if (_renderers.Count == 0) return;
if (_lastCore >= 0 && core.Current < _lastCore)
_flashLeft = FlashSeconds; // hit edge -> white-hot pop (the audio alarm fires beside it)
_lastCore = core.Current;
_flashLeft -= SystemAPI.Time.DeltaTime;
float frac = Mathf.Clamp01(core.Current / (float)core.Max);
float dim = Mathf.Lerp(0.35f, 1f, frac);
float flash = _flashLeft > 0f ? Mathf.Clamp01(_flashLeft / FlashSeconds) : 0f;
for (int i = 0; i < _renderers.Count; i++)
{
var r = _renderers[i];
if (r == null) continue;
var c0 = _authoredColors[i];
// Wounded shift: keep red-ish energy, drain green/blue with integrity (reads as bleeding light).
var wounded = new Color(
Mathf.Min(1f, c0.r * dim + (1f - frac) * 0.25f),
c0.g * dim * (0.45f + 0.55f * frac),
c0.b * dim * (0.45f + 0.55f * frac),
c0.a);
var col = flash > 0f ? Color.Lerp(wounded, Color.white, flash) : wounded;
r.GetPropertyBlock(_mpb);
_mpb.SetColor(BaseColorId, col);
r.SetPropertyBlock(_mpb);
}
}
void Resolve()
{
_resolved = true;
_mpb = new MaterialPropertyBlock();
foreach (var name in TargetNames)
{
var go = GameObject.Find(name);
if (go == null) continue;
foreach (var r in go.GetComponentsInChildren<Renderer>())
{
var m = r.sharedMaterial;
if (m == null || !m.HasProperty(BaseColorId)) continue;
_renderers.Add(r);
_authoredColors.Add(m.GetColor(BaseColorId));
}
}
}
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 47b2a19145125ac4e98411cab7f9b569
@@ -69,7 +69,7 @@ namespace ProjectM.Client
protected override void OnUpdate()
{
if (UnityEngine.SceneManagement.SceneManager.GetActiveScene().name != "Game") return;
if (!ScenePolicy.IsGameplayScene()) return;
if (!DecalConfig.Enabled) { if (_tracked.Count > 0) ClearAll(); return; }
EntityManager.CompleteDependencyBeforeRO<BlightClutter>();
@@ -55,7 +55,7 @@ namespace ProjectM.Client
protected override void OnUpdate()
{
if (UnityEngine.SceneManagement.SceneManager.GetActiveScene().name != "Game") return;
if (!ScenePolicy.IsGameplayScene()) return;
var cfg = DynamicLightConfig.Instance;
bool enabled = cfg == null || cfg.Enabled;
if (!enabled)
@@ -26,15 +26,12 @@ namespace ProjectM.Client
/// </summary>
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
[UpdateInGroup(typeof(PresentationSystemGroup))]
[UpdateAfter(typeof(OnboardingSystem))] // read OnboardingState.Active same-frame (single prompt voice)
public partial class HudSystem : SystemBase
{
// ---- palette (Aether language; Synty white skins are tinted into these) ----
static readonly Color AetherCyan = new(0.30f, 0.85f, 1f);
static readonly Color OreAmber = new(1f, 0.72f, 0.35f);
static readonly Color BioGreen = new(0.55f, 0.85f, 0.45f);
static readonly Color ChargeViolet = new(0.80f, 0.45f, 1f);
static readonly Color CoreRed = new(1f, 0.40f, 0.32f); // END-1 Engine Core integrity bar
static readonly Color PanelDark = new(0.08f, 0.11f, 0.15f, 0.90f);
static readonly Color PanelWarm = new(0.16f, 0.09f, 0.09f, 0.88f);
@@ -44,7 +41,6 @@ namespace ProjectM.Client
static readonly Color SlotIdleBg = new(0.09f, 0.11f, 0.15f, 0.92f);
static readonly Color SlotSelBg = new(0.16f, 0.26f, 0.32f, 0.95f);
static readonly Color SlotIdleBorder = new(1f, 1f, 1f, 0.08f);
const int MaxPips = 12;
const float ExpeditionRegionXMin = RegionMath.RegionBoundaryX; // camera x past this = the +1000 expedition region (DR-013)
GameObject _hudGo;
@@ -60,18 +56,9 @@ namespace ProjectM.Client
VisualElement _threatPanel, _threatIcon;
Label _threatNum;
// macro: banner + location + goal
VisualElement _banner, _goalContainer, _goalPipsRow, _goalBar, _goalFill;
Label _phaseText, _cycleText, _locationText, _goalText;
// END-1: Engine Core integrity (losable base-heart) + overrun flash edge-detector
VisualElement _coreContainer, _coreBar, _coreFill;
Label _coreText;
uint _lastOverrunTick;
float _overrunFlashLeft;
// END-2: terminal win/loss banner (observes the replicated RunOutcome; latched server-side).
VisualElement _runBanner;
Label _runBannerText, _runBannerSub;
// macro: banner + location line
VisualElement _banner;
Label _phaseText, _locationText;
// Demo polish: the clickable READY panel (Staging/Launching).
VisualElement _readyPanel, _readyPipRow;
Button _readyBtn;
@@ -85,19 +72,15 @@ namespace ProjectM.Client
VisualElement _depthPanel;
int _depthShownFor;
bool _depthBuilt;
VisualElement _outcomeFlash; // one-shot gold/red full-screen flash when the outcome banner first lands
float _outcomeFlashLeft;
byte _outcomeFlashedFor;
readonly List<VisualElement> _pips = new();
// resources
Label _aetherNum, _oreNum, _bioNum, _chargeNum;
Label _aetherNum, _oreNum, _bioNum;
// build palette + hints
VisualElement _paletteRow, _hintBar, _facingArrow, _buildDiscoveryChip;
bool _paletteBuilt, _hintBuilt, _hintConveyor;
VisualElement _paletteRow, _hintBar, _buildDiscoveryChip;
bool _paletteBuilt, _hintBuilt;
byte _hintScheme = 255;
readonly Dictionary<byte, PaletteItem> _palette = new();
@@ -151,92 +134,42 @@ namespace ProjectM.Client
// stays correct the day a Health/stats writer is parallelised.
EntityManager.CompleteDependencyBeforeRO<Health>();
EntityManager.CompleteDependencyBeforeRO<EffectiveCharacterStats>();
EntityManager.CompleteDependencyBeforeRO<EffectiveAbilityStats>();
EntityManager.CompleteDependencyBeforeRO<AbilityCooldown>();
EntityManager.CompleteDependencyBeforeRO<SocketCooldown>();
EntityManager.CompleteDependencyBeforeRO<EffectiveSocketStats>();
EntityManager.CompleteDependencyBeforeRO<RespawnInvuln>();
float dt = SystemAPI.Time.DeltaTime; // wall-frame delta — correct in a presentation system
bool haveTick = SystemAPI.TryGetSingleton<NetworkTime>(out var nt);
int huskCount = _huskQuery.CalculateEntityCount();
// ---- Macro: phase + cycle + countdown (center-top banner) ----
bool haveRun = SystemAPI.TryGetSingleton<RunInfo>(out var runInfo); // hoisted: the phase banner is lifecycle-aware (Phase 0 fix — it read "AT BASE" inside expedition rooms)
// B6: run-failed read (review-confirmed design: NEVER key on Returning — it is a 1-tick transient and
// the Charge bank lands a tick after it; detect the (in-run)->Staging edge with a launch-cached Charge.
// Lifecycle + Charge ride the SAME director ghost snapshot, so at the Staging edge the bank has arrived).
// ---- Macro banner: run-lifecycle header (the siege/cycle machinery is retired — LANTERN purge) ----
bool haveRun = SystemAPI.TryGetSingleton<RunInfo>(out var runInfo);
bool onRun = haveRun && runInfo.Lifecycle != RunLifecycle.Staging;
if (haveRun)
{
bool haveGoalNow = SystemAPI.TryGetSingleton<GoalProgress>(out var goalSnap);
byte lcNow = runInfo.Lifecycle;
if (lcNow == RunLifecycle.Launching && _prevRunLifecycle == RunLifecycle.Staging)
{
_chargeAtLaunch = haveGoalNow ? goalSnap.Charge : 0;
_wentInRun = false;
}
if (lcNow == RunLifecycle.InRoom) _wentInRun = true;
if (lcNow == RunLifecycle.Staging && _prevRunLifecycle != RunLifecycle.Staging && _wentInRun)
{
if (haveGoalNow && goalSnap.Charge <= _chargeAtLaunch)
_runFailedUntil = (float)SystemAPI.Time.ElapsedTime + 6f; // wipe/abort: nothing banked
_wentInRun = false;
}
_prevRunLifecycle = lcNow;
}
bool haveCycle = SystemAPI.TryGetSingleton<CycleState>(out var cyc);
bool siege = haveCycle && cyc.Phase == CyclePhase.Siege;
bool goalFull = SystemAPI.TryGetSingleton<GoalProgress>(out var goalNow) && goalNow.Target > 0 && goalNow.Charge >= goalNow.Target;
bool finalSiege = siege && goalFull; // END-2: the climactic final siege (goal cap reached)
bool onRun = haveRun && !siege && runInfo.Lifecycle != RunLifecycle.Staging; // mid-run: the base's Calm label is wrong
if (haveCycle)
{
var endTick = new NetworkTick(cyc.PhaseEndTick);
bool arming = haveTick && cyc.PhaseEndTick != 0 && endTick.IsValid && endTick.IsNewerThan(nt.ServerTick);
bool finalArming = !siege && goalFull && arming; // the cap-reached arming window before the final wave
int secs = arming ? (endTick.TicksSince(nt.ServerTick) / 60 + 1) : 0;
string detail;
if (siege)
detail = (finalSiege ? "FINAL SIEGE" : "WAVE " + cyc.WaveNumber) + " - " + huskCount + " HUSKS";
else if (arming)
detail = (finalArming ? "FINAL SIEGE INCOMING" : "INCURSION") + " - " + secs + "s";
else
detail = "";
// END-2: the climax reads distinct (intense red), not a normal incursion/wave.
var col = finalSiege || finalArming ? new Color(1f, 0.28f, 0.22f) : PhaseColor(cyc.Phase);
_phaseText.text = (finalSiege ? "HOLD THE ENGINE" : onRun ? "ON EXPEDITION" : PhaseLabel(cyc.Phase)) + (detail.Length > 0 ? " - " + detail : "");
var col = onRun ? new Color(1f, 0.8f, 0.4f) : new Color(0.45f, 0.9f, 0.7f);
_phaseText.text = onRun ? "ON EXPEDITION" : "AT BASE";
_phaseText.style.color = col;
_cycleText.text = "CYCLE " + cyc.CycleNumber;
_banner.style.borderBottomColor = col;
RetintPanel(_banner, siege ? PanelWarm : PanelDark);
RetintPanel(_banner, PanelDark);
}
else
{
_phaseText.text = "";
_cycleText.text = "";
}
// ---- Location line (banner sub-line) — Step 14: driven by the replicated RunInfo lifecycle FSM ----
// (the old camera-X + walk-in-gate copy died with the gate; siege/final overrides below still win).
var cam = Camera.main; // camera-X region signal still feeds downstream panels (atmosphere/threat)
bool onExpedition = cam != null && cam.transform.position.x > ExpeditionRegionXMin;
SystemAPI.TryGetSingleton<ExpeditionObjective>(out var obj);
if (haveRun && !siege && !finalSiege)
if (haveRun)
{
switch (runInfo.Lifecycle)
{
case RunLifecycle.Staging:
// The READY panel (bottom-center) owns the action + N/M count; the top line frames intent.
if ((float)SystemAPI.Time.ElapsedTime < _runFailedUntil)
{
// B6: a silent wipe used to land players home with ZERO explanation.
_locationText.text = "EXPEDITION FAILED - the party fell; nothing was banked";
_locationText.style.color = new Color(1f, 0.35f, 0.3f);
}
else
{
_locationText.text = "AT THE BASE - build defenses, buy upgrades, READY UP to launch";
_locationText.style.color = new Color(0.55f, 0.85f, 1f);
}
_locationText.text = "AT THE BASE - build defenses, buy upgrades, READY UP to launch";
_locationText.style.color = new Color(0.55f, 0.85f, 1f);
break;
case RunLifecycle.Launching:
{
@@ -281,33 +214,18 @@ namespace ProjectM.Client
break;
}
}
else if (!haveRun)
{
_locationText.text = finalSiege
? "FINAL SIEGE - hold the Engine, this is the last stand"
: siege ? "DEFEND THE BASE - hold the line"
: "MINE THE CRYSTALS - any attack harvests Ore, then BUILD";
_locationText.style.color = finalSiege ? new Color(1f, 0.3f, 0.25f)
: siege ? new Color(1f, 0.55f, 0.4f) : new Color(0.6f, 0.95f, 0.7f);
}
else
{
_locationText.text = finalSiege
? "FINAL SIEGE - hold the Engine, this is the last stand"
: "DEFEND THE BASE - hold the line";
_locationText.style.color = finalSiege ? new Color(1f, 0.3f, 0.25f) : new Color(1f, 0.55f, 0.4f);
_locationText.text = "";
}
// The clickable READY panel (Staging/Launching, hidden once the outcome latched — the banner owns
// the screen then). Counts are the replicated send-to-all PlayerReady flags.
// The clickable READY panel (Staging/Launching). Counts are the replicated send-to-all PlayerReady flags.
int rTotal = 0, rReady = 0;
bool localReady = false;
int launchSecs = 0;
bool terminal = SystemAPI.TryGetSingleton<RunOutcome>(out var readyOc)
&& readyOc.Value != RunOutcomeId.InProgress;
bool readyShow = haveRun && !terminal && !goalFull /* D6: goal full -> final defense armed, launching is refused server-side */
bool readyShow = haveRun
&& (runInfo.Lifecycle == RunLifecycle.Staging || runInfo.Lifecycle == RunLifecycle.Launching);
if (readyShow)
{
@@ -353,38 +271,9 @@ namespace ProjectM.Client
// Run-depth dots — keeps the roguelite spine visible while fighting (the map only shows at gates).
UpdateRunDepth(haveRun ? runInfo : default, haveRun);
// ---- Goal (hex-pip meter, or a continuous bar for large targets) ----
if (SystemAPI.TryGetSingleton<GoalProgress>(out var goal))
{
_goalContainer.style.display = DisplayStyle.Flex;
float gfrac = goal.Target > 0 ? Mathf.Clamp01(goal.Charge / (float)goal.Target) : 0f;
_goalText.text = "GOAL " + goal.Charge + " / " + goal.Target;
if (goal.Target >= 1 && goal.Target <= MaxPips)
{
_goalPipsRow.style.display = DisplayStyle.Flex;
_goalBar.style.display = DisplayStyle.None;
int active = Mathf.Min(goal.Charge, goal.Target); // Charge is the integer pip count; never over-fill
for (int i = 0; i < _pips.Count; i++)
{
bool show = i < goal.Target;
_pips[i].style.display = show ? DisplayStyle.Flex : DisplayStyle.None;
if (show) SetPip(_pips[i], i < active);
}
}
else
{
_goalPipsRow.style.display = DisplayStyle.None;
_goalBar.style.display = DisplayStyle.Flex;
HudUi.SetFill(_goalFill, gfrac);
}
}
else
{
_goalContainer.style.display = DisplayStyle.None;
}
// ---- Resources (feed palette affordability) ----
int aether = 0, ore = 0, bio = 0, charge = 0;
int aether = 0, ore = 0, bio = 0;
if (SystemAPI.TryGetSingletonEntity<ResourceLedger>(out var ledgerE))
{
var buf = SystemAPI.GetBuffer<StorageEntry>(ledgerE);
@@ -394,123 +283,28 @@ namespace ProjectM.Client
if (en.ItemId == ResourceId.Aether) aether = en.Count;
else if (en.ItemId == ResourceId.Ore) ore = en.Count;
else if (en.ItemId == ResourceId.Biomass) bio = en.Count;
else if (en.ItemId == ResourceId.Charge) charge = en.Count;
}
}
_aetherNum.text = aether.ToString();
_oreNum.text = ore.ToString();
_bioNum.text = bio.ToString();
_chargeNum.text = charge.ToString();
// DR-042 C6a: dim the Aether upgrade button when it isn't affordable (cost is a compile-time const).
// (Step 11: upgrade-button affordability tint retired with the button.)
// EB-2 quiet-turret cue (GLOBAL, not per-turret, so the deterministic Charge split never reads as one
// broken turret): a dry base during a siege tells the player to build a Fabricator.
if (siege && charge == 0 && !onExpedition)
{
_locationText.text = "TURRETS OUT OF AMMO - build a Fabricator (Ore -> ammo)";
_locationText.style.color = new Color(1f, 0.4f, 0.9f);
}
// ---- Engine Core integrity (END-1): a red base-heart bar; an overrun stamps a transient pulse we flash ----
if (SystemAPI.TryGetSingleton<CoreIntegrity>(out var core) && core.Max > 0)
{
_coreContainer.style.display = DisplayStyle.Flex;
float cfrac = Mathf.Clamp01(core.Current / (float)core.Max);
HudUi.SetFill(_coreFill, cfrac);
_coreText.text = "CORE " + core.Current + " / " + core.Max;
_coreText.style.color = Color.Lerp(BlightRed, CoreRed, cfrac); // shifts to danger as it drops
if (core.OverrunTick != 0 && core.OverrunTick != _lastOverrunTick)
{
_lastOverrunTick = core.OverrunTick; // edge-detect the replicated breach pulse
_overrunFlashLeft = 3.5f;
}
}
else
{
_coreContainer.style.display = DisplayStyle.None;
}
// Overrun flash overrides the location line (runs AFTER the EB-2 cue so it wins; at a breach Phase is Calm).
if (_overrunFlashLeft > 0f)
{
_overrunFlashLeft -= dt;
_locationText.text = "BASE OVERRUN - resources lost; the Core will recover";
_locationText.style.color = new Color(1f, 0.3f, 0.25f);
}
// First-run onboarding owns the prompt voice: while a coach-mark step is showing, blank the HUD's own
// location/gate hint so the player sees a single prompt (OnboardingSystem drives its own overlay).
if (OnboardingState.SuppressLocationLine) _locationText.text = ""; // D4: blank only for the early base-framing steps; room/siege/charge cues survive
// D6: goal full but the final siege hasn't spawned yet (the arming gap) -> the READY panel is hidden; tell the
// player what's coming instead of a stale base line (goalFull is replicated; RunPhase is server-only).
if (haveRun && goalFull && !terminal && !siege && !finalSiege)
{
_locationText.text = "GOAL REACHED - FINAL DEFENSE INCOMING: hold the Engine!";
_locationText.style.color = new Color(1f, 0.35f, 0.28f);
}
// ---- END-2: terminal run banner (Victory / Loss), observed from the replicated RunOutcome ----
if (SystemAPI.TryGetSingleton<RunOutcome>(out var runOutcome) && runOutcome.Value != RunOutcomeId.InProgress)
{
bool win = runOutcome.Value == RunOutcomeId.Victory;
if (_outcomeFlashedFor != runOutcome.Value)
{
// One-shot landing flourish: full-screen color flash + camera kick — the beat gets a payoff.
_outcomeFlashedFor = runOutcome.Value;
_outcomeFlashLeft = win ? 0.9f : 0.7f;
PrototypeCameraRig.PunchFov(win ? 5f : 3f, win ? 420f : 260f);
PrototypeCameraRig.AddShake(win ? 0.25f : 0.5f);
}
_runBanner.style.display = DisplayStyle.Flex;
_runBannerText.text = win ? "THE ENGINE HOLDS" : "OVERRUN";
_runBannerText.style.color = win ? new Color(0.45f, 0.95f, 1f) : new Color(1f, 0.35f, 0.3f);
_runBannerSub.text = win ? "VICTORY - the final siege is broken" : "THE FINAL STAND FELL";
_runBannerSub.style.color = win ? new Color(0.7f, 0.95f, 1f) : new Color(1f, 0.6f, 0.5f);
}
else
{
_runBanner.style.display = DisplayStyle.None;
_outcomeFlashedFor = 0;
}
// Outcome flash decay (lazy element; the banner dim sits above it, the world below).
if (_outcomeFlashLeft > 0f)
{
if (_outcomeFlash == null && _doc != null && _doc.rootVisualElement != null)
{
_outcomeFlash = new VisualElement { pickingMode = PickingMode.Ignore };
_outcomeFlash.style.position = Position.Absolute;
_outcomeFlash.style.left = 0; _outcomeFlash.style.right = 0;
_outcomeFlash.style.top = 0; _outcomeFlash.style.bottom = 0;
_doc.rootVisualElement.Add(_outcomeFlash);
}
_outcomeFlashLeft -= dt;
if (_outcomeFlash != null)
{
bool winFlash = _outcomeFlashedFor == RunOutcomeId.Victory;
var fc = winFlash ? new Color(1f, 0.85f, 0.35f) : new Color(1f, 0.20f, 0.15f);
_outcomeFlash.style.backgroundColor = new Color(fc.r, fc.g, fc.b, Mathf.Clamp01(_outcomeFlashLeft) * 0.35f);
_outcomeFlash.style.display = DisplayStyle.Flex;
}
}
else if (_outcomeFlash != null)
{
_outcomeFlash.style.display = DisplayStyle.None;
}
// ---- Threat readout (top-right) — hidden entirely at base with zero husks; its reappearance is the cue ----
bool showThreat = siege || huskCount > 0;
// ---- Threat readout (top-right) — hidden entirely with zero husks; its reappearance is the cue ----
bool showThreat = huskCount > 0;
_threatPanel.style.display = showThreat ? DisplayStyle.Flex : DisplayStyle.None;
if (showThreat)
{
float intensity = Mathf.Clamp01(huskCount / 30f);
Color tc = siege ? Color.Lerp(ThreatWarm, BlightRed, intensity) : ThreatWarm;
Color tc = Color.Lerp(ThreatWarm, BlightRed, intensity);
_threatNum.text = huskCount.ToString();
_threatNum.style.color = tc;
_threatIcon.style.unityBackgroundImageTintColor = tc;
RetintPanel(_threatPanel, siege ? PanelWarm : PanelDark);
RetintPanel(_threatPanel, PanelDark);
}
// ---- Build palette + control hints (bottom-center) ----
@@ -520,10 +314,7 @@ namespace ProjectM.Client
if (buildActive)
{
byte scheme = AimPresentation.Scheme;
bool conv = BuildPaletteState.Selected == StructureType.Conveyor;
if (!_hintBuilt || _hintScheme != scheme || _hintConveyor != conv) RebuildHints(scheme, conv);
if (conv && _facingArrow != null)
_facingArrow.style.rotate = new StyleRotate(new Rotate(new Angle(FacingDegrees(BuildPaletteState.Direction))));
if (!_hintBuilt || _hintScheme != scheme) RebuildHints(scheme);
_hintBar.style.display = DisplayStyle.Flex;
}
else
@@ -539,9 +330,9 @@ namespace ProjectM.Client
float hp = 0f, maxHp = 1f, cdFrac = 1f;
bool dead = false, shielded = false;
foreach (var (health, effChar, effAbility, cd, invuln, entity) in
SystemAPI.Query<RefRO<Health>, RefRO<EffectiveCharacterStats>, RefRO<EffectiveAbilityStats>,
RefRO<AbilityCooldown>, RefRO<RespawnInvuln>>()
foreach (var (health, effChar, cd, invuln, entity) in
SystemAPI.Query<RefRO<Health>, RefRO<EffectiveCharacterStats>,
RefRO<SocketCooldown>, RefRO<RespawnInvuln>>()
.WithAll<GhostOwnerIsLocal, PlayerTag>().WithEntityAccess())
{
found = true;
@@ -549,8 +340,15 @@ namespace ProjectM.Client
maxHp = effChar.ValueRO.MaxHealth > 0f ? effChar.ValueRO.MaxHealth : health.ValueRO.Max;
dead = SystemAPI.IsComponentEnabled<Dead>(entity);
uint nextFire = cd.ValueRO.NextFireTick;
int cdTicks = effAbility.ValueRO.CooldownTicks;
// Cooldown bar = socket 0 (the primary Spark) of the 4-socket kit (the legacy single
// AbilityCooldown died — LANTERN purge). EffectiveSocketStats row 0 supplies the duration.
uint nextFire = cd.ValueRO.Get(0);
int cdTicks = 0;
if (SystemAPI.HasBuffer<EffectiveSocketStats>(entity))
{
var effSockets = SystemAPI.GetBuffer<EffectiveSocketStats>(entity);
if (effSockets.Length > 0) cdTicks = effSockets[0].CooldownTicks;
}
var nextTick = new NetworkTick(nextFire);
cdFrac = (haveTick && nextFire != 0 && cdTicks > 0 && nextTick.IsValid && nextTick.IsNewerThan(nt.ServerTick))
? Mathf.Clamp01(1f - nextTick.TicksSince(nt.ServerTick) / (float)cdTicks)
@@ -562,7 +360,7 @@ namespace ProjectM.Client
break;
}
_doc.rootVisualElement.style.display = (found || haveCycle) ? DisplayStyle.Flex : DisplayStyle.None;
_doc.rootVisualElement.style.display = (found || haveRun) ? DisplayStyle.Flex : DisplayStyle.None;
// ---- Low-health vignette + hurt flash (full-screen) ----
_flash = HudVisualMath.DecayFlash(_flash, dt);
@@ -661,27 +459,10 @@ namespace ProjectM.Client
else p.style.backgroundColor = c;
}
void SetPip(VisualElement pip, bool active)
{
var theme = HudTheme.Get();
var spr = active ? theme?.PipActive : theme?.PipInactive;
if (spr != null)
{
pip.style.backgroundImage = new StyleBackground(Background.FromSprite(spr));
pip.style.unityBackgroundImageTintColor = active ? AetherCyan : PipDim;
pip.style.backgroundSize = new StyleBackgroundSize(new BackgroundSize(BackgroundSizeType.Contain));
}
else
{
pip.style.backgroundColor = active ? AetherCyan : PipDim;
MenuUi.Round(pip, 3);
}
}
// DR-042 C6d: Harvester/Conveyor/Pylon are dead (unwired automation) -> hidden from the build palette
// (catalog + prefabs stay baked, code-intact per DR-020). Only Turret/Wall/Fabricator are buildable in the UI.
static bool IsPaletteType(byte type) =>
type != StructureType.Pylon && type != StructureType.Harvester && type != StructureType.Conveyor;
// LANTERN purge: the automation buildables are deleted; Pylon stays hidden from the build palette (cosmetic-only).
static bool IsPaletteType(byte type) => type != StructureType.Pylon;
void UpdatePalette(int aether, int ore, int bio, bool onExpedition)
{
@@ -769,30 +550,15 @@ namespace ProjectM.Client
_palette[type] = new PaletteItem { Root = root, Cost = costLabel, CostAmount = cost, CostRes = costRes, Glow = glow, Icon = iconEl };
}
void RebuildHints(byte scheme, bool conveyor)
void RebuildHints(byte scheme)
{
_hintBar.Clear();
_facingArrow = null;
var theme = HudTheme.Get();
bool pad = scheme == InputSchemeId.Gamepad;
AddHint(pad ? theme?.PadPlace : theme?.KbmPlace, pad ? "A" : "LMB", "PLACE");
AddHint(pad ? theme?.PadCancel : theme?.KbmCancel, pad ? "B" : "RMB", "CANCEL");
if (conveyor)
{
// Rotate hint + a LIVE facing arrow (resolves the DR-021 conveyor-facing indicator). Only conveyors
// rotate, so this chip is gated to them — the other buildables don't show a meaningless ROTATE.
var chip = MakeChip();
chip.Add(HudUi.Glyph(pad ? theme?.PadRotate : null, pad ? "LB" : "R", 26));
var lbl = HudUi.Text("FACING", 12, MenuUi.SubCol, TextAnchor.MiddleLeft);
lbl.style.marginLeft = 5; lbl.style.marginRight = 6;
chip.Add(lbl);
_facingArrow = HudUi.Icon(theme != null ? theme.ConveyorIcon : null, 24, AetherCyan);
chip.Add(_facingArrow);
_hintBar.Add(chip);
}
AddHint(pad ? theme?.PadExit : null, pad ? "MENU" : "ESC", "EXIT");
_hintScheme = scheme;
_hintConveyor = conveyor;
_hintBuilt = true;
}
@@ -837,7 +603,6 @@ namespace ProjectM.Client
BuildDiscoveryChip(root);
BuildDowned(root);
BuildInventory(root);
BuildRunBanner(root);
}
void BuildVignette(VisualElement root)
@@ -965,69 +730,13 @@ namespace ProjectM.Client
_banner.Add(bIcon);
_phaseText = HudUi.Display("", 30, AetherCyan, TextAnchor.MiddleCenter);
_banner.Add(_phaseText);
_cycleText = HudUi.Text("", 14, MenuUi.SubCol, TextAnchor.MiddleCenter);
_cycleText.style.marginLeft = 14;
_banner.Add(_cycleText);
macro.Add(_banner);
_locationText = HudUi.Text("", 15, new Color(0.6f, 0.85f, 1f), TextAnchor.MiddleCenter);
_locationText.style.marginTop = 5;
macro.Add(_locationText);
// goal: hex-pip meter (or fallback bar) + numeral
_goalContainer = HudUi.Group(Align.Center);
_goalContainer.style.marginTop = 8;
var goalLine = new VisualElement();
goalLine.style.flexDirection = FlexDirection.Row;
goalLine.style.alignItems = Align.Center;
goalLine.pickingMode = PickingMode.Ignore;
_goalPipsRow = new VisualElement();
_goalPipsRow.style.flexDirection = FlexDirection.Row;
_goalPipsRow.style.alignItems = Align.Center;
_goalPipsRow.pickingMode = PickingMode.Ignore;
for (int i = 0; i < MaxPips; i++)
{
var pip = new VisualElement();
pip.style.width = 22; pip.style.height = 22;
pip.style.marginLeft = 2; pip.style.marginRight = 2;
pip.style.flexShrink = 0;
pip.pickingMode = PickingMode.Ignore;
pip.style.display = DisplayStyle.None;
_pips.Add(pip);
_goalPipsRow.Add(pip);
}
goalLine.Add(_goalPipsRow);
_goalText = HudUi.Display("GOAL 0 / 10", 16, AetherCyan, TextAnchor.MiddleCenter);
_goalText.style.marginLeft = 10;
goalLine.Add(_goalText);
_goalContainer.Add(goalLine);
// fallback continuous bar (large targets)
_goalBar = HudUi.Bar(360, 16, new Color(0.8f, 0.6f, 1f), out _goalFill);
_goalBar.style.marginTop = 4;
_goalBar.style.display = DisplayStyle.None;
_goalContainer.Add(_goalBar);
macro.Add(_goalContainer);
// END-1: Engine Core integrity bar (red) — the losable base-heart meter.
_coreContainer = HudUi.Group(Align.Center);
_coreContainer.style.marginTop = 6;
var coreLine = new VisualElement();
coreLine.style.flexDirection = FlexDirection.Row;
coreLine.style.alignItems = Align.Center;
coreLine.pickingMode = PickingMode.Ignore;
_coreBar = HudUi.Bar(360, 14, CoreRed, out _coreFill);
coreLine.Add(_coreBar);
_coreText = HudUi.Text("CORE 100 / 100", 13, CoreRed, TextAnchor.MiddleLeft);
_coreText.style.marginLeft = 10;
coreLine.Add(_coreText);
_coreContainer.Add(coreLine);
_coreContainer.style.display = DisplayStyle.None;
macro.Add(_coreContainer);
root.Add(macro);
}
@@ -1046,7 +755,6 @@ namespace ProjectM.Client
strip.Add(ResourceChip(theme != null ? theme.AetherIcon : null, AetherCyan, "0", out _aetherNum, 26, 20));
strip.Add(ResourceChip(theme != null ? theme.OreIcon : null, OreAmber, "0", out _oreNum, 30, 22));
strip.Add(ResourceChip(theme != null ? theme.BioIcon : null, BioGreen, "0", out _bioNum, 26, 20));
strip.Add(ResourceChip(null, ChargeViolet, "0", out _chargeNum, 26, 20)); // EB-2 turret ammo (flat violet, no icon)
// DR-042 C6a: the only Aether sink (ability-damage upgrade) gets a visible, clickable button (was U-key
// only). The Button element handles its own picking even though the HUD root Ignores clicks.
// (Step 11: the Aether UPGRADE-DMG button was RETIRED with AbilityUpgradeRequest — the choice-of-3
@@ -1142,53 +850,7 @@ namespace ProjectM.Client
_downed.style.display = DisplayStyle.None;
root.Add(_downed);
}
void BuildRunBanner(VisualElement root)
{
_runBanner = new VisualElement();
_runBanner.style.position = Position.Absolute;
_runBanner.style.left = 0; _runBanner.style.right = 0; _runBanner.style.top = 0; _runBanner.style.bottom = 0;
_runBanner.style.alignItems = Align.Center;
_runBanner.style.justifyContent = Justify.Center;
_runBanner.pickingMode = PickingMode.Ignore;
_runBanner.style.backgroundColor = new Color(0.02f, 0.03f, 0.05f, 0.55f);
var col = HudUi.Group(Align.Center);
_runBannerText = HudUi.Display("", 72, Color.white, TextAnchor.MiddleCenter);
col.Add(_runBannerText);
_runBannerSub = HudUi.Text("", 22, MenuUi.SubCol, TextAnchor.MiddleCenter);
_runBannerSub.style.marginTop = 8;
col.Add(_runBannerSub);
// END-2 (SL-5): the terminal banner offers a clear action so the player isn't hunting for Esc.
// SINGLE: PLAY AGAIN Continues as a fresh campaign (base+meta kept — the terminal save rolls
// forward on stage). CO-OP (operator-locked): the honest exit is a clean teardown for everyone —
// the host ends the session (each joiner's ConnectionWatchdog returns them to the menu with a
// reason), a joiner just leaves. All self-guard on WorldLauncher.Busy. The row picks (Position)
// even though the banner root Ignores.
var btnRow = new VisualElement();
btnRow.style.flexDirection = FlexDirection.Row;
btnRow.style.justifyContent = Justify.Center;
btnRow.style.marginTop = 28;
btnRow.pickingMode = PickingMode.Position;
switch (WorldLauncher.LastMode)
{
case SessionMode.Host:
btnRow.Add(MenuUi.Button("END SESSION — ALL TO MENU", WorldLauncher.TeardownToMenu));
break;
case SessionMode.Join:
btnRow.Add(MenuUi.Button("LEAVE TO MENU", WorldLauncher.TeardownToMenu));
break;
default:
var again = MenuUi.Button("PLAY AGAIN",
() => WorldLauncher.StartSession(SessionMode.Single, null, SaveService.HasSave()));
again.style.marginRight = 12;
btnRow.Add(again);
btnRow.Add(MenuUi.Button("QUIT TO MENU", WorldLauncher.TeardownToMenu));
break;
}
col.Add(btnRow);
_runBanner.Add(col);
_runBanner.style.display = DisplayStyle.None;
root.Add(_runBanner);
}
void BuildInventory(VisualElement root)
@@ -1313,48 +975,17 @@ namespace ProjectM.Client
return resId == ResourceId.Aether ? t.AetherIcon : resId == ResourceId.Biomass ? t.BioIcon : t.OreIcon;
}
// Conveyor facing (BuildPaletteState.Direction 0=+X,1=-X,2=+Z,3=-Z) → arrow rotation; the arrow art points up (+Z).
static float FacingDegrees(byte dir)
{
switch (dir)
{
case 0: return 90f; // +X
case 1: return 270f; // -X
case 3: return 180f; // -Z
default: return 0f; // +Z
}
}
static Color PhaseColor(byte phase)
{
switch (phase)
{
case CyclePhase.Calm: return new Color(0.45f, 0.9f, 0.7f);
case CyclePhase.Siege: return new Color(1f, 0.45f, 0.3f);
default: return Color.white;
}
}
static string PhaseLabel(byte phase)
{
switch (phase)
{
case CyclePhase.Calm: return "AT BASE";
case CyclePhase.Siege: return "UNDER SIEGE";
default: return "";
}
}
static string StructureName(byte type)
{
switch (type)
{
case StructureType.Turret: return "Turret";
case StructureType.Wall: return "Wall";
case StructureType.Pylon: return "Pylon";
case StructureType.Harvester: return "Harvester";
case StructureType.Fabricator: return "Fabricator";
case StructureType.Conveyor: return "Conveyor";
default: return "?";
}
}
@@ -1558,11 +1189,6 @@ namespace ProjectM.Client
}
// B6 run-failed read (client-local edge state; see the tracker near the top of OnUpdate).
byte _prevRunLifecycle;
int _chargeAtLaunch;
bool _wentInRun;
float _runFailedUntil;
}
}
@@ -60,8 +60,6 @@ namespace ProjectM.Client
}
bool haveRun = SystemAPI.TryGetSingleton<RunInfo>(out var runInfo);
bool haveCycle = SystemAPI.TryGetSingleton<CycleState>(out var cyc);
bool siege = haveCycle && cyc.Phase == CyclePhase.Siege;
// Aether from the ledger (the sole meta-shop currency; last entry wins, matching the core loop).
int aether = 0;
@@ -76,9 +74,9 @@ namespace ProjectM.Client
// server-only); tiers from the replicated MetaTierState record on the director ghost.
byte localClass = ClassTraits.WarriorClass;
bool haveLocalPlayer = false;
foreach (var (fr, ar) in SystemAPI.Query<RefRO<FrameId>, RefRO<AbilityRef>>().WithAll<PlayerTag, GhostOwnerIsLocal>())
foreach (var fr in SystemAPI.Query<RefRO<FrameId>>().WithAll<PlayerTag, GhostOwnerIsLocal>())
{
localClass = fr.ValueRO.Value != 0 ? fr.ValueRO.Value : ClassTraits.ClassForAbility(ar.ValueRO.Id); // FrameId signal, AbilityRef fallback
localClass = ClassTraits.Normalize(fr.ValueRO.Value); // FrameId is the sole class signal (legacy AbilityRef deleted)
haveLocalPlayer = true;
break;
}
@@ -86,7 +84,7 @@ namespace ProjectM.Client
bool metaShow = false;
BlobAssetReference<MetaUpgradeCatalogBlob> metaPool = default;
DynamicBuffer<MetaTierState> metaRecord = default;
if (haveRun && runInfo.Lifecycle == RunLifecycle.Staging && haveLocalPlayer && !siege
if (haveRun && runInfo.Lifecycle == RunLifecycle.Staging && haveLocalPlayer
&& SystemAPI.TryGetSingleton<MetaUpgradeCatalog>(out var metaCat) && metaCat.Value.IsCreated
&& SystemAPI.TryGetSingletonBuffer<MetaTierState>(out metaRecord, true))
{
@@ -11,9 +11,8 @@ namespace ProjectM.Client
/// every AudioSource starts the same frame with loop=true and identical clip length, and per-bar amplitude
/// envelopes reach ~zero at each bar boundary so both the chord changes and the loop point are click-free
/// (the <c>AmbientAudioSystem.Snap</c> trick generalized to enveloped segments). The MIX is the state
/// machine: layer volumes ease toward targets chosen from replicated state only — <see cref="RunInfo"/>
/// lifecycle (staging / combat / boss / reward lull), <see cref="CycleState"/> siege, and the terminal
/// <see cref="RunOutcome"/> (one-shot victory/defeat sting + aftermath bed). Observe-only presentation
/// machine: layer volumes ease toward targets chosen from replicated state only — the <see cref="RunInfo"/>
/// lifecycle (staging / launch / combat / boss / reward lull / return). Observe-only presentation
/// system: no sim writes, no determinism surface; asset-free per the project convention. Sits under SFX at
/// <see cref="MasterVolume"/> × <see cref="GameVolume.Music"/>; the low <c>AmbientAudioSystem</c> drone
/// (vol 0.10) remains as texture beneath it.
@@ -43,7 +42,6 @@ namespace ProjectM.Client
GameObject _root;
AudioSource _bass, _pad, _arp, _pulse;
float _vBass, _vPad, _vArp, _vPulse; // current smoothed volumes (pre-master)
byte _outcomePlayed; // which terminal outcome's sting has fired (0 = none)
protected override void OnStartRunning()
{
@@ -80,27 +78,7 @@ namespace ProjectM.Client
// ---- pick the mix from replicated state (defaults = quiet staging bed) ----
float tBass = 0.50f, tPad = 0.55f, tArp = 0.12f, tPulse = 0f;
bool haveRun = SystemAPI.TryGetSingleton<RunInfo>(out var run);
SystemAPI.TryGetSingleton<CycleState>(out var cyc);
bool siege = cyc.Phase == CyclePhase.Siege;
bool finalSiege = siege && SystemAPI.TryGetSingleton<GoalProgress>(out var goal)
&& goal.Target > 0 && goal.Charge >= goal.Target;
byte outcome = SystemAPI.TryGetSingleton<RunOutcome>(out var oc) ? oc.Value : RunOutcomeId.InProgress;
if (outcome != RunOutcomeId.InProgress)
{
if (_outcomePlayed != outcome)
{
_outcomePlayed = outcome;
PlayOutcomeSting(outcome == RunOutcomeId.Victory);
}
tBass = 0.20f; tPad = 0.45f; tArp = 0f; tPulse = 0f; // aftermath bed under the banner
}
else if (siege)
{
tBass = 0.70f; tPad = 0.40f; tArp = 0.60f; tPulse = finalSiege ? 0.90f : 0.65f;
}
else if (haveRun)
if (SystemAPI.TryGetSingleton<RunInfo>(out var run))
{
switch (run.Lifecycle)
{
@@ -138,15 +116,7 @@ namespace ProjectM.Client
_pulse.volume = _vPulse * master;
}
void PlayOutcomeSting(bool victory)
{
// Victory: rising A-minor->major-feel arpeggio; defeat: falling minor third crawl.
float[] notes = victory
? new[] { 220f, 277.18f, 329.63f, 440f } // A3 C#4 E4 A4 (picardy lift)
: new[] { 220f, 207.65f, 174.61f, 146.83f }; // A3 Ab3 F3 D3
var clip = BuildStingClip(notes, victory ? 0.16f : 0.28f, victory ? 0.45f : 0.40f);
if (_pad != null) _pad.PlayOneShot(clip, 0.9f * GameVolume.Music);
}
// ================= clip builders (deterministic, asset-free) =================
@@ -263,23 +233,6 @@ namespace ProjectM.Client
return clip;
}
static AudioClip BuildStingClip(float[] notes, float noteLen, float vol)
{
int len = (int)(notes.Length * noteLen * SampleRate) + SampleRate / 2; // + half-second tail
var clip = AudioClip.Create("music_sting", len, 1, SampleRate, false);
var data = new float[len];
for (int n = 0; n < notes.Length; n++)
{
int start = (int)(n * noteLen * SampleRate);
int dur = (int)(SampleRate * (noteLen + (n == notes.Length - 1 ? 0.5f : 0.05f)));
for (int i = 0; i < dur && start + i < len; i++)
{
float tn = i / (float)SampleRate;
data[start + i] += Mathf.Sin(2f * Mathf.PI * notes[n] * tn) * Mathf.Exp(-4.5f * tn) * vol;
}
}
clip.SetData(data, 0);
return clip;
}
}
}
@@ -50,7 +50,7 @@ namespace ProjectM.Client
protected override void OnUpdate()
{
if (UnityEngine.SceneManagement.SceneManager.GetActiveScene().name != "Game") return;
if (!ScenePolicy.IsGameplayScene()) return;
if (_root == null) return;
var cfg = RoomDressingConfig.Instance;
@@ -0,0 +1,19 @@
using UnityEngine.SceneManagement;
namespace ProjectM.Client
{
/// <summary>
/// The ONE place client presentation systems ask "is this a gameplay-look scene?" — replaces the old
/// scattered <c>scene.name == "Game"</c> string gates (LANTERN realignment: Game and DevSandbox share
/// the same look; MainMenu/ArtStaging stay untouched by the dynamic look systems).
/// </summary>
public static class ScenePolicy
{
/// <summary>True in the scenes that carry the LANTERN gameplay look (Game + DevSandbox).</summary>
public static bool IsGameplayScene()
{
var name = SceneManager.GetActiveScene().name;
return name == "Game" || name == "DevSandbox";
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f4e4ab296b48b204f866c561e9acd11d
@@ -62,7 +62,7 @@ namespace ProjectM.Client
protected override void OnUpdate()
{
if (UnityEngine.SceneManagement.SceneManager.GetActiveScene().name != "Game") { Pending.Clear(); return; }
if (!ScenePolicy.IsGameplayScene()) { Pending.Clear(); return; }
if (!DecalConfig.Enabled)
{
Pending.Clear();
@@ -4,31 +4,26 @@ using UnityEngine;
namespace ProjectM.Client
{
/// <summary>
/// Client-only ATMOSPHERE cross-fade for the two-region world. A managed presentation system
/// (SystemBase, main thread, NO Burst) in <see cref="PresentationSystemGroup"/> that OBSERVES the
/// follow-camera's world-space X and lerps <see cref="RenderSettings"/> fog + Trilight sky-ambient between
/// the lush cyan meadow base (origin) and the hostile orange arid expedition (+1000 X) — mirroring
/// HudSystem's region split at camera X &gt; 500. The directional light + skybox + post-FX volume stay
/// GLOBAL; only fog colour/density + ambient sky colour shift per region. Pure presentation: never mutates
/// the simulation, no ECS query (camera + RenderSettings only). Knobs live in
/// <see cref="WorldAtmosphereConfig"/> (falls back to baked defaults when absent). Guarded to the gameplay
/// "Game" scene so it never restyles the menu. Fog enable/mode + equator/ground ambient are baked into the
/// scene; this only drives the per-region deltas. Phase 1.5b: also drives a per-room-biome tint on the
/// expedition ground quads (MaterialPropertyBlock — the material asset is never touched).
/// Client-only water-column ATMOSPHERE for the LANTERN seabed (deep-sea murk). A managed presentation
/// system (SystemBase, main thread, NO Burst) in <see cref="PresentationSystemGroup"/> that OBSERVES the
/// follow-camera's world-space X and lerps <see cref="RenderSettings"/> fog + flat ambient between the
/// base-shelf murk (origin) and the deeper out-shelf murk (+1000 X) — mirroring HudSystem's region split
/// at camera X &gt; 500. The skybox stays empty and the post-FX volume GLOBAL; only fog colour/density +
/// ambient shift per region. Pure presentation: never mutates the simulation. Knobs live in
/// <see cref="WorldAtmosphereConfig"/> (falls back to baked LANTERN-murk defaults when absent). Gated by
/// <see cref="ScenePolicy.IsGameplayScene"/> so it never restyles the menu or ArtStaging. Per-room-biome
/// murk variants key on the replicated RunInfo.CurrentBiome (re-meant to depth/water flavors — the old
/// meadow/arid/blight palettes died in the LANTERN realignment).
/// </summary>
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
[UpdateInGroup(typeof(PresentationSystemGroup))]
public partial class WorldAtmosphereSystem : SystemBase
{
Camera _cam;
MeshRenderer[] _ground;
MaterialPropertyBlock _mpb;
Color _groundCur;
static readonly int BaseColorId = Shader.PropertyToID("_BaseColor");
protected override void OnUpdate()
{
if (UnityEngine.SceneManagement.SceneManager.GetActiveScene().name != "Game") return;
if (!ScenePolicy.IsGameplayScene()) return;
if (_cam == null) _cam = Camera.main;
if (_cam == null) return;
@@ -36,39 +31,34 @@ namespace ProjectM.Client
float boundary = cfg != null ? cfg.BoundaryX : 500f;
float half = cfg != null ? Mathf.Max(1f, cfg.BlendHalfWidth) : 80f;
Color baseFog = cfg != null ? cfg.BaseFogColor : new Color(0.09f, 0.13f, 0.14f, 1f);
float baseDen = cfg != null ? cfg.BaseFogDensity : 0.010f;
Color baseAmb = cfg != null ? cfg.BaseAmbientSky : new Color(0.32f, 0.38f, 0.48f, 1f);
Color expFog = cfg != null ? cfg.ExpeditionFogColor : new Color(0.24f, 0.13f, 0.06f, 1f);
float expDen = cfg != null ? cfg.ExpeditionFogDensity : 0.016f;
Color expAmb = cfg != null ? cfg.ExpeditionAmbientSky : new Color(0.45f, 0.31f, 0.20f, 1f);
// Step 14 (expedition redesign): the EXPEDITION-side palette keys on the replicated per-room biome
// (RunInfo.CurrentBiome) so every room re-themes — the camera-X blend still owns the base↔run fade.
// Palettes are code consts (cosmetic; per-biome config knobs can layer on later without churn).
// Phase 1.5: all palettes moved to the dark-ambient set (dynamic lights carry readability).
// Ground tints are ABSOLUTE _BaseColor values (the MPB replaces the authored arid sand tint).
Color groundTarget = new Color(0.60f, 0.56f, 0.50f, 1f); // authored arid sand (Mat_Ground_Arid_Dark)
// LANTERN murk defaults (match the ArtStaging source-of-truth hues; density is the play-readability knob).
Color baseFog = cfg != null ? cfg.BaseFogColor : new Color(0.02f, 0.10f, 0.12f, 1f);
float baseDen = cfg != null ? cfg.BaseFogDensity : 0.035f;
Color baseAmb = cfg != null ? cfg.BaseAmbientSky : new Color(0.03f, 0.055f, 0.08f, 1f);
Color expFog = cfg != null ? cfg.ExpeditionFogColor : new Color(0.015f, 0.075f, 0.10f, 1f);
float expDen = cfg != null ? cfg.ExpeditionFogDensity : 0.04f;
Color expAmb = cfg != null ? cfg.ExpeditionAmbientSky : new Color(0.02f, 0.045f, 0.07f, 1f);
// Per-room murk flavors (cosmetic code consts; all stay inside the deep-sea band — hue shifts are
// subtle, value stays dark so dynamic lights keep carrying readability).
bool haveRun = SystemAPI.TryGetSingleton<ProjectM.Simulation.RunInfo>(out var runInfo);
if (haveRun && runInfo.Lifecycle != ProjectM.Simulation.RunLifecycle.Staging)
{
switch (runInfo.CurrentBiome)
{
case ProjectM.Simulation.RoomBiomeId.Meadow:
expFog = new Color(0.10f, 0.16f, 0.10f, 1f); expDen = 0.012f;
expAmb = new Color(0.30f, 0.42f, 0.32f, 1f);
groundTarget = new Color(0.47f, 0.55f, 0.43f, 1f);
case ProjectM.Simulation.RoomBiomeId.Meadow: // kelp shallows: a touch greener
expFog = new Color(0.02f, 0.10f, 0.09f, 1f); expDen = 0.035f;
expAmb = new Color(0.025f, 0.06f, 0.06f, 1f);
break;
case ProjectM.Simulation.RoomBiomeId.Cavern:
expFog = new Color(0.06f, 0.08f, 0.12f, 1f); expDen = 0.020f;
expAmb = new Color(0.24f, 0.28f, 0.38f, 1f);
groundTarget = new Color(0.42f, 0.46f, 0.55f, 1f);
case ProjectM.Simulation.RoomBiomeId.Cavern: // crush-dark trench: colder + denser
expFog = new Color(0.01f, 0.05f, 0.08f, 1f); expDen = 0.05f;
expAmb = new Color(0.015f, 0.035f, 0.06f, 1f);
break;
case ProjectM.Simulation.RoomBiomeId.Blight:
expFog = new Color(0.13f, 0.07f, 0.15f, 1f); expDen = 0.022f;
expAmb = new Color(0.32f, 0.24f, 0.38f, 1f);
groundTarget = new Color(0.52f, 0.42f, 0.58f, 1f);
case ProjectM.Simulation.RoomBiomeId.Blight: // gloam bloom: a sick green-violet cast
expFog = new Color(0.045f, 0.075f, 0.09f, 1f); expDen = 0.045f;
expAmb = new Color(0.045f, 0.05f, 0.075f, 1f);
break;
// Arid keeps the config/default burnt-dusk orange + sand ground above.
// default keeps the config/fallback out-shelf murk above.
}
}
@@ -79,27 +69,6 @@ namespace ProjectM.Client
RenderSettings.fogColor = Color.Lerp(baseFog, expFog, t);
RenderSettings.fogDensity = Mathf.Lerp(baseDen, expDen, t);
RenderSettings.ambientSkyColor = Color.Lerp(baseAmb, expAmb, t);
// Per-room-biome ground tint (expedition quads only; the meadow base keeps its authored material).
if (_ground == null)
{
var found = new System.Collections.Generic.List<MeshRenderer>();
foreach (var mr in Object.FindObjectsByType<MeshRenderer>(FindObjectsSortMode.None))
if (mr.gameObject.name == "Ground_Arid") found.Add(mr);
if (found.Count > 0)
{
_ground = found.ToArray();
_mpb = new MaterialPropertyBlock();
_groundCur = groundTarget; // snap on first sight — no lerp from black
}
}
if (_ground != null)
{
_groundCur = Color.Lerp(_groundCur, groundTarget, 1f - Mathf.Exp(-2.5f * UnityEngine.Time.deltaTime));
_mpb.SetColor(BaseColorId, _groundCur);
for (int i = 0; i < _ground.Length; i++)
if (_ground[i] != null) _ground[i].SetPropertyBlock(_mpb);
}
}
}
}
@@ -16,7 +16,7 @@ namespace ProjectM.Client
/// </summary>
public static class HowToPlayPanel
{
static readonly string[] Tabs = { "Controls", "The Loop", "Build & Economy", "Threats", "Win / Lose" };
static readonly string[] Tabs = { "Controls", "The Loop", "Build & Economy", "Threats" };
public static VisualElement Build(Action onClose)
{
@@ -68,7 +68,7 @@ namespace ProjectM.Client
switch (idx)
{
case 0: // Controls (chosen class)
bool ranger = WorldLauncher.SelectedClass == (byte)CharacterId.Ranger;
bool ranger = WorldLauncher.SelectedClass == (byte)FrameKind.Ranger;
Head(c, ranger ? "CLASS: Ranger (ranged anchor)" : "CLASS: Warrior (melee anchor)");
Body(c, "Move — WASD / Left Stick");
Body(c, "Aim — Mouse cursor / Right Stick");
@@ -85,35 +85,24 @@ namespace ProjectM.Client
Body(c, "Pause — Esc");
break;
case 1: // The Loop
Head(c, "THE LOOP — expedition RUNS are how you win");
Body(c, "1. BASE — pick your CLASS + buy PREP buffs (this run only); build Turrets + a Fabricator; spend Aether on PERMANENT class upgrades.");
Head(c, "THE LOOP — descend, fight, bank");
Body(c, "1. BASE — pick your suit-frame, buy PREP buffs (this run only), spend Aether on PERMANENT upgrades.");
Body(c, "2. READY UP [T] — when everyone is ready, the party launches into a multi-room run together.");
Body(c, "3. RUN — clear each room, pick 1 of 3 BOONS (this run only), choose your path on the map, mine the crystals.");
Body(c, "4. BOSS — fell it and the party returns with the haul: +1 on the Engine meter. A wipe banks nothing.");
Body(c, "5. SIEGE — a completed run provokes retaliation at base. Defend the Core!");
Body(c, "6. WIN — fill the Engine meter, then hold the final siege.");
Body(c, "3. RUN — clear each room, pick 1 of 3 BOONS (this run only), choose your path, mine the crystals.");
Body(c, "4. BOSS — fell it and the party returns with the haul. A wipe banks nothing but the depth record.");
break;
case 2: // Build & Economy
Head(c, "RESOURCES & BUILDING");
Body(c, "Ore — main currency. Attack the glowing crystals INSIDE runs and haul them home (they're scarce — spend well).");
Body(c, "Turret (40 Ore) — auto-fires at enemies. Needs Charge as ammo.");
Body(c, "Fabricator (30 Ore) — converts Ore → Charge so turrets keep firing.");
Body(c, "Wall (Biomass) — a cheap barrier that blocks enemies.");
Body(c, "Aether — rare; fuels PERMANENT class upgrades at the base between runs.");
Body(c, "Aether — rare; fuels PERMANENT upgrades at the base between runs.");
Body(c, "Open Build with Tab (Y), pick a piece, click a green tile to place it.");
break;
case 3: // Threats
default: // Threats
Head(c, "THREATS");
Body(c, "Husks assault the base during a Siege — keep them off the Engine Core.");
Body(c, "A Husk that reaches the Core drains it. Lose the Core in the final siege and the run ends.");
Body(c, "Runs escalate room by room — elites guard the deep paths, a boss guards the end.");
Body(c, "Enemy variety scales the deeper you push.");
break;
default: // Win / Lose
Head(c, "WIN / LOSE");
Body(c, "WIN — clear expeditions to fill the Engine meter, then hold the final siege.");
Body(c, "LOSE — the Engine Core falls during the final siege.");
Body(c, "A Core breach mid-run is only a setback: resources lost, the Core recovers in Calm.");
Body(c, "What the light doesn't hold, the dark takes back.");
break;
}
}
@@ -48,17 +48,11 @@ namespace ProjectM.Client
public Sprite BioIcon;
[Header("Structure icons")]
public Sprite TurretIcon;
public Sprite WallIcon;
public Sprite PylonIcon;
public Sprite HarvesterIcon;
public Sprite FabricatorIcon;
public Sprite ConveyorIcon;
[Header("Build-ghost preview meshes (authored real-size, ground pivot — BuildSendSystem)")]
public Mesh TurretGhostMesh;
public Mesh WallGhostMesh;
public Mesh FabricatorGhostMesh;
[Header("Build-mode control glyphs")]
public Sprite KbmPlace; // LMB
@@ -86,12 +80,8 @@ namespace ProjectM.Client
{
switch (type)
{
case StructureType.Turret: return TurretIcon;
case StructureType.Wall: return WallIcon;
case StructureType.Pylon: return PylonIcon;
case StructureType.Harvester: return HarvesterIcon;
case StructureType.Fabricator: return FabricatorIcon;
case StructureType.Conveyor: return ConveyorIcon;
default: return null;
}
}
@@ -101,9 +91,7 @@ namespace ProjectM.Client
{
switch (type)
{
case StructureType.Turret: return TurretGhostMesh;
case StructureType.Wall: return WallGhostMesh;
case StructureType.Fabricator: return FabricatorGhostMesh;
default: return null;
}
}
@@ -99,8 +99,8 @@ namespace ProjectM.Client
var classRow = new VisualElement();
classRow.style.flexDirection = FlexDirection.Row;
classRow.style.justifyContent = Justify.Center;
classRow.Add(MenuUi.Button("Warrior", () => SelectClass((byte)CharacterId.Warrior)));
classRow.Add(MenuUi.Button("Ranger", () => SelectClass((byte)CharacterId.Ranger)));
classRow.Add(MenuUi.Button("Warrior", () => SelectClass((byte)FrameKind.Warrior)));
classRow.Add(MenuUi.Button("Ranger", () => SelectClass((byte)FrameKind.Ranger)));
card.Add(classRow);
card.Add(MenuUi.Button("Single Player", () => Launch(SessionMode.Single, false)));
@@ -149,7 +149,7 @@ namespace ProjectM.Client
if (_classLabel != null) _classLabel.text = ClassName(classId);
}
static string ClassName(byte classId) => classId == (byte)CharacterId.Ranger ? "CLASS: Ranger (ranged)" : "CLASS: Warrior (melee)";
static string ClassName(byte classId) => classId == (byte)FrameKind.Ranger ? "CLASS: Ranger (ranged)" : "CLASS: Warrior (melee)";
void ShowSettings()
{
@@ -28,8 +28,8 @@ namespace ProjectM.Client
public static bool Busy { get; private set; }
/// <summary>Slice 2: the class chosen in the menu (a CharacterId byte), seeded into the client world at session start.</summary>
public static byte SelectedClass = (byte)CharacterId.Warrior;
/// <summary>Slice 2: the frame chosen in the menu (a FrameKind byte), seeded into the client world at session start.</summary>
public static byte SelectedClass = (byte)FrameKind.Warrior;
/// <summary>The mode of the most recently started session — the HUD's outcome banner branches on it
/// (single: PLAY AGAIN restarts; co-op: the honest exit is a clean teardown for everyone).</summary>
@@ -129,17 +129,11 @@ namespace ProjectM.Client
static void StagePendingSave(World server)
{
var data = SaveService.Load();
// A terminal (won/lost) save Continues as a FRESH campaign — base+meta kept, outcome/meter/core reset
// (else the dead outcome banner re-latches from the first snapshot).
SaveService.RollTerminalCampaignForward(data);
if (data == null) return;
var em = server.EntityManager;
var e = em.CreateEntity();
// v5->v6 migration (operator-approved): an old save's Charge counted boss-cleared runs (DR-042), so a
// missing RunsCompleted floors to it — the HUD never shows "Charge 3/4" beside "Runs completed: 0".
int runsCompleted = data.RunsCompleted > data.GoalCharge ? data.RunsCompleted : data.GoalCharge;
em.AddComponentData(e, new PendingSave { GoalCharge = data.GoalCharge, GoalTarget = data.GoalTarget, CoreCurrent = data.CoreCurrent, RunOutcome = (byte)data.RunOutcome, RunsCompleted = runsCompleted, MaxDepthReached = data.MaxDepthReached, HasData = 1 });
em.AddComponentData(e, new PendingSave { RunsCompleted = data.RunsCompleted, MaxDepthReached = data.MaxDepthReached, HasData = 1 });
// v6: stage the meta tiers UNCONDITIONALLY (empty OK — the Bursted spawn system GetBuffers it in the
// HasData block; a conditional buffer would throw on any v<=5 Continue). Rows verbatim, no clamping.
var mbuf = em.AddBuffer<PendingMetaRow>(e);
@@ -159,10 +153,6 @@ namespace ProjectM.Client
var sbuf = em.AddBuffer<PendingStructure>(se);
foreach (var s in data.Structures)
sbuf.Add(SaveApply.ToPending(s)); // EB-1: pure mapping (unit-tested, incl. the wounded HP)
var iobuf = em.AddBuffer<PendingStructureIo>(se);
if (data.StructureIo != null)
foreach (var io in data.StructureIo)
iobuf.Add(new PendingStructureIo { StructureIndex = io.StructureIndex, Slot = io.Slot, ResourceId = io.ResourceId, Count = io.Count });
}
}
@@ -175,9 +165,6 @@ namespace ProjectM.Client
using var q = em.CreateEntityQuery(ComponentType.ReadOnly<ResourceLedger>());
if (q.IsEmptyIgnoreFilter) return;
var dir = q.GetSingletonEntity();
var goal = em.HasComponent<GoalProgress>(dir) ? em.GetComponentData<GoalProgress>(dir) : default;
var core = em.HasComponent<CoreIntegrity>(dir) ? em.GetComponentData<CoreIntegrity>(dir) : default; // END-1
var outcome = em.HasComponent<RunOutcome>(dir) ? em.GetComponentData<RunOutcome>(dir) : default; // END-2
var buffer = em.GetBuffer<StorageEntry>(dir, true);
var rows = new LedgerRow[buffer.Length];
@@ -195,21 +182,16 @@ namespace ProjectM.Client
// v6: the permanent-meta slice via the ONE shared collector — omitting it HERE (the most common
// exit path) would silently WIPE all meta progression on quit (the meta review's top blocker).
MetaSaveScan.Collect(em, dir, out var metaRows, out var runsCompleted, out var maxDepth);
SaveStructureScan.Collect(em, nowTick, out var structures, out var structureIo);
SaveStructureScan.Collect(em, nowTick, out var structures);
SaveService.Save(new SaveData
{
GoalCharge = goal.Charge,
GoalTarget = goal.Target,
CoreCurrent = core.Current,
RunsCompleted = runsCompleted,
MaxDepthReached = maxDepth,
MetaUpgrades = metaRows,
RunOutcome = outcome.Value,
Ledger = rows,
Structures = structures,
StructureIo = structureIo,
SavedAtMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
});
}
@@ -62,6 +62,71 @@ namespace ProjectM.EditorTools
Debug.Log("[PlayerRigTools] AC_PlayerTopDown: IsAttacking + idle-based MeleeSwing built (no un-keyed-bone collapse).");
}
/// <summary>LANTERN P5 (realignment): swap the Bathynaut suit body onto Player.prefab IN PLACE (GUID
/// preserved — the subscene PlayerSpawner refs never break). Mirrors the proven EnemyRigTools.BuildOne
/// recipe: strip the old Synty-soldier skeleton + SMR children (every ROOT component — ghost/authoring/
/// CC/Animator/RigDefinitionAuthoring — stays), flatten the suit body's skeleton + SMRs under the player
/// root, keep the feet-on-ground Root offset, re-slot the deformation material, and adopt the clone's
/// avatar (same shared humanoid Characters avatar — AC_PlayerTopDown's muscle clips retarget). Both
/// frames share this visual until per-frame player models land. Idempotent / re-runnable.</summary>
[MenuItem("ProjectM/Animation/Player - Build Bathynaut Player (LANTERN)")]
public static void BuildBathynautPlayer()
{
const string synty = "Assets/Synty/PolygonSciFiSpace/Prefabs/Characters/SM_Chr_SpaceSoldier_HelmetArmor_Male_01.prefab";
const string matPath = "Assets/_Project/Materials/M_SuitFrame_Bathynaut_Animated.mat";
const string output = "Assets/_Project/Prefabs/Player.prefab";
const float rootY = -0.90f; // the player's existing feet-on-ground offset (entity origin = capsule center)
var syntyAsset = AssetDatabase.LoadAssetAtPath<GameObject>(synty);
var mat = AssetDatabase.LoadAssetAtPath<Material>(matPath);
if (syntyAsset == null) { Debug.LogError($"[PlayerRigTools] Suit body prefab missing: {synty}"); return; }
if (mat == null) { Debug.LogError($"[PlayerRigTools] Deformation material missing: {matPath}"); return; }
var root = PrefabUtility.LoadPrefabContents(output);
try
{
// Strip ONLY the old visual (skeleton + SMR children); root components stay untouched.
var kids = new System.Collections.Generic.List<Transform>();
foreach (Transform c in root.transform) kids.Add(c);
foreach (var c in kids) Object.DestroyImmediate(c.gameObject);
// Clone the suit body (unlinked) and flatten its skeleton + SMRs under the player root.
var clone = (GameObject)Object.Instantiate(syntyAsset);
var srcAnimator = clone.GetComponentInChildren<Animator>();
var avatar = srcAnimator != null ? srcAnimator.avatar : null;
var children = new System.Collections.Generic.List<Transform>();
foreach (Transform c in clone.transform) children.Add(c);
foreach (var c in children) c.SetParent(root.transform, false); // keep local transforms
Object.DestroyImmediate(clone);
// Feet-on-ground: offset the un-keyed Root bone.
var rootBone = root.transform.Find("Root");
if (rootBone != null) { var lp = rootBone.localPosition; lp.y = rootY; rootBone.localPosition = lp; }
else Debug.LogWarning("[PlayerRigTools] No 'Root' bone after the swap; feet offset skipped.");
// Deformation material on every SMR slot (else unskinned-static under Entities Graphics).
foreach (var smr in root.GetComponentsInChildren<SkinnedMeshRenderer>(true))
{
var mats = smr.sharedMaterials;
for (int i = 0; i < mats.Length; i++) mats[i] = mat;
smr.sharedMaterials = mats;
}
var anim = root.GetComponent<Animator>();
if (anim != null && avatar != null) anim.avatar = avatar; // controller (AC_PlayerTopDown) stays
PrefabUtility.SaveAsPrefabAsset(root, output);
Debug.Log("[PlayerRigTools] Bathynaut suit body swapped onto Player.prefab (in place).");
}
finally
{
PrefabUtility.UnloadPrefabContents(root);
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
static AnimationClip FindIdleClip(AnimatorController ac)
{
foreach (var c in ac.layers[0].stateMachine.states)
@@ -10,13 +10,11 @@ namespace ProjectM.Server
{
/// <summary>
/// One-shot server restore of player-built structures for a "Continue" session. The menu (WorldLauncher) stages a
/// <see cref="PendingStructure"/>/<see cref="PendingStructureIo"/> carrier in the fresh ServerWorld BEFORE the
/// gameplay subscene streams; this system waits (RequireForUpdate) for the streamed <see cref="StructureCatalog"/>
/// + <see cref="BaseAnchor"/> + a valid NetworkTime, then replays each saved structure CHARGE-FREE: Instantiate the
/// catalog prefab at the saved cell (preserving the baked Scale), re-stamp the rebased tick fields
/// (<see cref="ProductionMath.RestoreNextTick"/>; LastProcessed = now so within-session catch-up resumes from now,
/// never a wall-clock mint), re-tag RegionTag{Base} + RuntimePlacedTag, refill the in-flight conveyor item + the
/// machine I/O buffers, then DESTROY the carrier so it never runs again. The ledger/goal restore separately +
/// <see cref="PendingStructure"/> carrier in the fresh ServerWorld BEFORE the gameplay subscene streams; this
/// system waits (RequireForUpdate) for the streamed <see cref="StructureCatalog"/> + <see cref="BaseAnchor"/> +
/// a valid NetworkTime, then replays each saved structure CHARGE-FREE: Instantiate the catalog prefab at the
/// saved cell (preserving the baked Scale), restore the wounded HP born-correct, re-tag RegionTag{Base} +
/// RuntimePlacedTag, then DESTROY the carrier so it never runs again. The ledger restores separately +
/// absolutely via CycleDirectorSpawnSystem's born-correct load (no double-spend, no Withdraw here).
/// </summary>
[BurstCompile]
@@ -24,14 +22,12 @@ namespace ProjectM.Server
public partial struct BaseRestoreSystem : ISystem
{
ComponentLookup<LocalTransform> m_TransformLookup;
ComponentLookup<Conveyor> m_ConveyorLookup;
ComponentLookup<Health> m_HealthLookup;
[BurstCompile]
public void OnCreate(ref SystemState state)
{
m_TransformLookup = state.GetComponentLookup<LocalTransform>(isReadOnly: true);
m_ConveyorLookup = state.GetComponentLookup<Conveyor>(isReadOnly: true);
m_HealthLookup = state.GetComponentLookup<Health>(isReadOnly: true);
state.RequireForUpdate<StructureCatalog>();
state.RequireForUpdate<BaseAnchor>();
@@ -48,7 +44,6 @@ namespace ProjectM.Server
uint now = serverTick.TickIndexForValidTick;
m_TransformLookup.Update(ref state);
m_ConveyorLookup.Update(ref state);
m_HealthLookup.Update(ref state);
var anchor = SystemAPI.GetSingleton<BaseAnchor>();
@@ -56,8 +51,8 @@ namespace ProjectM.Server
var ecb = new EntityCommandBuffer(Allocator.Temp);
foreach (var (pending, ioBuf, carrier) in
SystemAPI.Query<DynamicBuffer<PendingStructure>, DynamicBuffer<PendingStructureIo>>().WithEntityAccess())
foreach (var (pending, carrier) in
SystemAPI.Query<DynamicBuffer<PendingStructure>>().WithEntityAccess())
{
for (int s = 0; s < pending.Length; s++)
{
@@ -81,12 +76,12 @@ namespace ProjectM.Server
{
Type = p.Type,
Cell = cell,
NextTick = ProductionMath.RestoreNextTick(now, p.RemainingTicks),
NextTick = 0u, // cooldown restore retired with the automation chain (LANTERN purge)
LastProcessedTick = TickUtil.NonZero(now),
});
// EB-1: restore the wounded HP born-correct in the SAME ecb as Instantiate (Health.Current is a
// [GhostField]; a deferred set would leak baked Max to clients for one snapshot). Max + the
// 0->full fallback come from the BAKED prefab, never the save. Automation machines lack Health.
// 0->full fallback come from the BAKED prefab, never the save.
if (m_HealthLookup.HasComponent(prefab))
{
var hm = m_HealthLookup[prefab];
@@ -94,35 +89,6 @@ namespace ProjectM.Server
}
ecb.AddComponent(structure, new RegionTag { Region = RegionId.Base });
ecb.AddComponent<RuntimePlacedTag>(structure);
if (p.Type == StructureType.Conveyor && m_ConveyorLookup.HasComponent(prefab))
{
var conv = m_ConveyorLookup[prefab];
conv.Direction = p.Direction;
ecb.SetComponent(structure, conv);
ecb.SetComponent(structure, new ConveyorItem { ResourceId = p.ConveyorResId, Count = p.ConveyorCount });
ecb.SetComponentEnabled<ConveyorItem>(structure, p.ConveyorCount > 0);
}
// Refill machine I/O buffers from the flat io table (only slots with saved rows -> the prefab has them).
bool inInit = false, outInit = false;
DynamicBuffer<MachineInput> inBuf = default;
DynamicBuffer<MachineOutput> outBuf = default;
for (int r = 0; r < ioBuf.Length; r++)
{
if (ioBuf[r].StructureIndex != s)
continue;
if (ioBuf[r].Slot == 0)
{
if (!inInit) { inBuf = ecb.SetBuffer<MachineInput>(structure); inInit = true; }
inBuf.Add(new MachineInput { ResourceId = ioBuf[r].ResourceId, Count = ioBuf[r].Count });
}
else
{
if (!outInit) { outBuf = ecb.SetBuffer<MachineOutput>(structure); outInit = true; }
outBuf.Add(new MachineOutput { ResourceId = ioBuf[r].ResourceId, Count = ioBuf[r].Count });
}
}
}
ecb.DestroyEntity(carrier);
@@ -1,276 +0,0 @@
using ProjectM.Simulation;
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.NetCode;
namespace ProjectM.Server
{
/// <summary>
/// Server-only, deterministic conveyor transport — the MIDDLE of the M7 auto-gather chain
/// (Harvester → Conveyor → Fabricator). Unlike the per-machine catch-up production systems, a conveyor is a
/// single TRANSPORT STEP: each period-due, empty <see cref="Conveyor"/> first PULLS one item off an adjacent
/// upstream <see cref="MachineOutput"/> (the cell at <c>myCell DirOffset(dir)</c> — i.e. the machine feeding
/// INTO this belt) onto its own <see cref="ConveyorItem"/>; then every loaded conveyor advances its item
/// EXACTLY one cell toward <c>myCell + DirOffset(dir)</c>. The move resolution is delegated to the pure,
/// unit-tested <see cref="ConveyorMath.ResolveMoves"/> so determinism is provable WITHOUT a world: sources are
/// processed sorted by <see cref="ConveyorMath.CellKey"/> (NOT hashmap order), occupancy is read from a
/// pre-move double-buffer snapshot, a destination conveyor cell accepts at most one item (only if it was empty
/// in the snapshot; ties → lowest CellKey wins, losers stall with no silent loss), and machine-input sink
/// cells always accept (deposit). Sinks are a separate set so an item leaving the belt into a fabricator's
/// <see cref="MachineInput"/> never collides with belt occupancy.
/// <para>
/// Mirrors <c>TurretFireSystem</c>'s now-extraction (<c>NetworkTime.ServerTick.TickIndexForValidTick</c>) +
/// <see cref="PlacedStructure.NextTick"/> cooldown idiom (each conveyor is period-gated the same way), and
/// <c>ResourceHarvestSystem</c>'s Temp-collection foreach idiom. Runs in the plain server
/// <c>SimulationSystemGroup</c> <c>[UpdateAfter(HarvesterProductionSystem)]</c> (after harvesters deposit, so
/// fresh output is pull-eligible this tick; before the fabricator consumes). All buffer/enableable mutation is
/// in place (toggling an enableable bit is NOT a structural change) → no ECB.
/// </para>
/// </summary>
[BurstCompile]
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
[UpdateInGroup(typeof(SimulationSystemGroup))]
[UpdateAfter(typeof(HarvesterProductionSystem))]
public partial struct ConveyorTransportSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<NetworkTime>();
state.RequireForUpdate(state.GetEntityQuery(ComponentType.ReadOnly<Conveyor>()));
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
var serverTick = SystemAPI.GetSingleton<NetworkTime>().ServerTick;
if (!serverTick.IsValid)
return;
uint now = serverTick.TickIndexForValidTick;
// ---- Snapshot every conveyor once (entity, cell, direction, item, period-due) ----
var convEntity = new NativeList<Entity>(Allocator.Temp);
var convCell = new NativeList<int2>(Allocator.Temp);
var convDir = new NativeList<byte>(Allocator.Temp);
var convItemRes = new NativeList<int>(Allocator.Temp); // 0 = empty
var convItemCnt = new NativeList<int>(Allocator.Temp); // 0 = empty
var convDue = new NativeList<bool>(Allocator.Temp); // period-gate satisfied this tick
foreach (var (ps, conveyor, e) in
SystemAPI.Query<RefRW<PlacedStructure>, RefRO<Conveyor>>().WithEntityAccess())
{
int period = math.max(1, conveyor.ValueRO.PeriodTicks);
// Period-gate each conveyor through NextTick exactly like the production systems. A never-processed
// belt initialises its baseline this tick and is NOT due (mirrors NeedsInit on the machines).
bool due;
if (ProductionMath.NeedsInit(ps.ValueRO.LastProcessedTick))
{
ps.ValueRW.LastProcessedTick = TickUtil.NonZero(now);
ps.ValueRW.NextTick = TickUtil.NonZero(now + (uint)period);
due = false;
}
else
{
int cycles = ProductionMath.CyclesDue(
serverTick, ps.ValueRO.NextTick, ps.ValueRO.LastProcessedTick, period, Tuning.MaxProductionCatchup);
due = cycles > 0;
if (due)
{
// A belt moves at most one cell per period; collapse any catch-up to a single step but keep
// the baseline advancing so it re-evaluates next period.
ps.ValueRW.LastProcessedTick = TickUtil.NonZero(now);
ps.ValueRW.NextTick = TickUtil.NonZero(now + (uint)period);
}
}
int res = 0, cnt = 0;
if (SystemAPI.IsComponentEnabled<ConveyorItem>(e))
{
var item = SystemAPI.GetComponent<ConveyorItem>(e);
if (item.Count > 0)
{
res = item.ResourceId;
cnt = item.Count;
}
}
convEntity.Add(e);
convCell.Add(ps.ValueRO.Cell);
convDir.Add(conveyor.ValueRO.Direction);
convItemRes.Add(res);
convItemCnt.Add(cnt);
convDue.Add(due);
}
int n = convEntity.Length;
// Cell → conveyor snapshot index (belt occupancy map for ResolveMoves + the pull lookup).
var cellToIndex = new NativeHashMap<int2, int>(n, Allocator.Temp);
for (int i = 0; i < n; i++)
cellToIndex.TryAdd(convCell[i], i); // duplicate cells can't occur (one structure per cell)
// Sink cells = cells hosting a machine-input buffer (fabricators); these always accept a deposit. Map
// each sink cell to its owning entity so an arriving item can be deposited into its MachineInput.
var sinkCells = new NativeHashSet<int2>(8, Allocator.Temp);
var sinkCellToEntity = new NativeHashMap<int2, Entity>(8, Allocator.Temp);
foreach (var (ps, _, e) in
SystemAPI.Query<RefRO<PlacedStructure>, DynamicBuffer<MachineInput>>().WithEntityAccess())
{
sinkCells.Add(ps.ValueRO.Cell);
sinkCellToEntity.TryAdd(ps.ValueRO.Cell, e);
}
// Source cells = cells hosting a machine-OUTPUT buffer (harvesters/fabricators) a belt can pull from.
// Built once so the pull phase is a single hash lookup per belt (no nested per-belt query).
var outputCellToEntity = new NativeHashMap<int2, Entity>(8, Allocator.Temp);
foreach (var (ps, _, e) in
SystemAPI.Query<RefRO<PlacedStructure>, DynamicBuffer<MachineOutput>>().WithEntityAccess())
{
outputCellToEntity.TryAdd(ps.ValueRO.Cell, e);
}
// ---- PULL: each empty, due belt draws one item off an adjacent UPSTREAM MachineOutput ----
// Upstream cell = myCell DirOffset(dir): the machine feeding INTO this belt sits there. We pull a
// single unit so a harvester's buffered output flows onto the belt one item per period.
for (int i = 0; i < n; i++)
{
if (!convDue[i] || convItemCnt[i] > 0)
continue;
int2 srcCell = convCell[i] - ConveyorMath.DirOffset(convDir[i]);
// The feeder must be a machine with a MachineOutput buffer (harvester or fabricator), NOT another
// conveyor (belts hand off in the move phase, not via pull). Single hash lookup on the prebuilt map.
if (!outputCellToEntity.TryGetValue(srcCell, out var feeder))
continue;
var output = SystemAPI.GetBuffer<MachineOutput>(feeder);
// Pull the first available resource row off the feeder (deterministic: first non-empty row order).
byte pulledId = 0;
for (int r = 0; r < output.Length; r++)
{
if (output[r].ResourceId != 0 && output[r].Count > 0)
{
pulledId = output[r].ResourceId;
break;
}
}
if (pulledId == 0)
continue;
int taken = MachineSlotMath.Withdraw(output, pulledId, 1);
if (taken <= 0)
continue;
// Load the belt in the snapshot so it participates in THIS tick's move resolution.
convItemRes[i] = pulledId;
convItemCnt[i] = taken;
}
// ---- MOVE: resolve all belt advances from the pre-move (post-pull) snapshot, then apply ----
var srcCells = new NativeArray<int2>(n, Allocator.Temp);
var dirs = new NativeArray<byte>(n, Allocator.Temp);
var itemRes = new NativeArray<int>(n, Allocator.Temp);
var itemCnt = new NativeArray<int>(n, Allocator.Temp);
for (int i = 0; i < n; i++)
{
srcCells[i] = convCell[i];
dirs[i] = convDir[i];
// Pass the FULL post-pull occupancy (due AND non-due) so the resolver blocks a due belt from moving
// INTO an occupied non-due cell. Non-due belts must still not ADVANCE themselves — that is enforced
// after resolution by skipping any returned move whose source belt is not due this tick.
itemRes[i] = convItemRes[i];
itemCnt[i] = convItemCnt[i];
}
var outMoveDst = new NativeArray<int2>(n, Allocator.Temp);
var outMoveSrcIdx = new NativeArray<int>(n, Allocator.Temp);
ConveyorMath.ResolveMoves(
srcCells, dirs, itemRes, itemCnt,
cellToIndex, sinkCells,
outMoveDst, outMoveSrcIdx, out int moveCount);
// Track which belts END this tick holding an item so we can settle enableable bits exactly once.
var endRes = new NativeArray<int>(n, Allocator.Temp);
var endCnt = new NativeArray<int>(n, Allocator.Temp);
for (int i = 0; i < n; i++)
{
// Default: every snapshot item stays put (stalls / non-due / no valid move). Moves below override.
endRes[i] = convItemRes[i];
endCnt[i] = convItemCnt[i];
}
for (int m = 0; m < moveCount; m++)
{
int srcIdx = outMoveSrcIdx[m];
// A non-due belt contributes its occupancy to the snapshot (so due belts can't overrun it) but must
// NOT advance its own item — skip its move and leave its item parked (the endRes/endCnt defaults).
if (!convDue[srcIdx])
continue;
int2 dst = outMoveDst[m];
int movRes = convItemRes[srcIdx];
int movCnt = convItemCnt[srcIdx];
// The source belt empties (its item left this cell).
endRes[srcIdx] = 0;
endCnt[srcIdx] = 0;
if (sinkCells.Contains(dst))
{
// Item leaves the belt network into a machine input slot.
if (sinkCellToEntity.TryGetValue(dst, out var sinkEntity))
{
var input = SystemAPI.GetBuffer<MachineInput>(sinkEntity);
MachineSlotMath.Deposit(input, (byte)movRes, movCnt);
}
}
else if (cellToIndex.TryGetValue(dst, out int dstIdx))
{
// Item advances onto the next belt cell (resolver guaranteed it was empty in the snapshot).
endRes[dstIdx] = movRes;
endCnt[dstIdx] = movCnt;
}
}
// ---- Settle each conveyor's ConveyorItem to its end-of-tick state (single write per belt) ----
for (int i = 0; i < n; i++)
{
var e = convEntity[i];
if (endCnt[i] > 0)
{
SystemAPI.SetComponent(e, new ConveyorItem { ResourceId = (byte)endRes[i], Count = endCnt[i] });
SystemAPI.SetComponentEnabled<ConveyorItem>(e, true);
}
else
{
SystemAPI.SetComponent(e, new ConveyorItem { ResourceId = 0, Count = 0 });
SystemAPI.SetComponentEnabled<ConveyorItem>(e, false);
}
}
convEntity.Dispose();
convCell.Dispose();
convDir.Dispose();
convItemRes.Dispose();
convItemCnt.Dispose();
convDue.Dispose();
cellToIndex.Dispose();
sinkCells.Dispose();
sinkCellToEntity.Dispose();
outputCellToEntity.Dispose();
srcCells.Dispose();
dirs.Dispose();
itemRes.Dispose();
itemCnt.Dispose();
outMoveDst.Dispose();
outMoveSrcIdx.Dispose();
endRes.Dispose();
endCnt.Dispose();
}
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 002c28988137cb945b9ffaccbb6d645f
@@ -1,104 +0,0 @@
using ProjectM.Simulation;
using Unity.Burst;
using Unity.Entities;
using Unity.Mathematics;
using Unity.NetCode;
namespace ProjectM.Server
{
/// <summary>
/// Server-only, deterministic fabricator production — the BACK of the M7 auto-gather chain
/// (Harvester → Conveyor → Fabricator). Each <see cref="Fabricator"/> consumes
/// <see cref="Fabricator.InAmount"/> of its (byte) input resource from its OWN server-only
/// <see cref="MachineInput"/> buffer (filled by an upstream conveyor) and, on a
/// <see cref="Fabricator.PeriodTicks"/> cadence, deposits <see cref="Fabricator.OutAmount"/> of its output
/// resource into the GLOBAL ledger — so a self-running base compounds its stockpile. Resolves the ledger via
/// <c>GetSingletonEntity&lt;ResourceLedger&gt;()</c> → <c>GetBuffer&lt;StorageEntry&gt;()</c> (NEVER
/// <c>GetSingleton&lt;StorageEntry&gt;</c> — a second StorageEntry buffer exists on the base container).
/// Mirrors <c>TurretFireSystem</c>'s now-extraction + cooldown idiom and <c>ResourceHarvestSystem</c>'s ledger
/// resolve; runs in the plain server <c>SimulationSystemGroup</c>
/// <c>[UpdateAfter(ConveyorTransportSystem)]</c> (which itself is after the harvester + the predicted group),
/// so a single tick can harvest → transport → fabricate in chain order. In-place buffer/ledger mutation
/// (not structural) → no ECB.
/// <para>
/// SINGLE GATED CATCH-UP PATH, INPUT-LIMITED (no mint-from-nothing): when due, the awarded
/// <see cref="ProductionMath.CyclesDue"/> cycles are further clamped to what the buffered input can afford
/// (<c>floor(TotalOf(input,InResourceId)/InAmount)</c>). The tick fields are re-stamped EVERY due period
/// regardless of <c>runs</c> (even a starved fabricator advances its baseline so it re-evaluates next period,
/// not on the next tick — preventing a busy retry storm). Offline catch-up is within-session tick math; never
/// wall-clock.
/// </para>
/// </summary>
[BurstCompile]
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
[UpdateInGroup(typeof(SimulationSystemGroup))]
[UpdateAfter(typeof(ConveyorTransportSystem))]
public partial struct FabricatorProductionSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<NetworkTime>();
state.RequireForUpdate<ResourceLedger>();
state.RequireForUpdate(state.GetEntityQuery(ComponentType.ReadOnly<Fabricator>()));
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
var serverTick = SystemAPI.GetSingleton<NetworkTime>().ServerTick;
if (!serverTick.IsValid)
return;
uint now = serverTick.TickIndexForValidTick;
var ledgerEntity = SystemAPI.GetSingletonEntity<ResourceLedger>();
var ledger = SystemAPI.GetBuffer<StorageEntry>(ledgerEntity);
foreach (var (ps, fab, input) in
SystemAPI.Query<RefRW<PlacedStructure>, RefRO<Fabricator>, DynamicBuffer<MachineInput>>())
{
int period = fab.ValueRO.PeriodTicks; // CyclesDue clamps to max(1, period)
// Never-processed (baked/just-placed) machine: initialise the catch-up baseline, produce nothing.
if (ProductionMath.NeedsInit(ps.ValueRO.LastProcessedTick))
{
ps.ValueRW.LastProcessedTick = TickUtil.NonZero(now);
ps.ValueRW.NextTick = TickUtil.NonZero(now + (uint)System.Math.Max(1, period));
continue;
}
int cycles = ProductionMath.CyclesDue(
serverTick, ps.ValueRO.NextTick, ps.ValueRO.LastProcessedTick, period, Tuning.MaxProductionCatchup);
if (cycles <= 0)
continue; // still cooling down / nothing due
byte inId = fab.ValueRO.InResourceId;
int inAmount = fab.ValueRO.InAmount;
// Input-limited: never produce more than the available input affords (no mint-from-nothing). EB-2:
// a ledger-fed Fabricator (InputFromLedger != 0) sources its input from the SHARED ledger (read LIVE
// here so a 2nd ledger-fed Fabricator sees the 1st's same-tick withdrawal) instead of MachineInput;
// both modes deposit the output to the ledger. A zero/negative input amount is unsatisfiable.
bool fromLedger = fab.ValueRO.InputFromLedger != 0;
int available = fromLedger ? StorageMath.TotalOf(ledger, inId) : MachineSlotMath.TotalOf(input, inId);
int affordable = inAmount > 0 ? available / inAmount : 0;
int runs = math.min(cycles, affordable);
if (runs > 0)
{
if (fromLedger)
StorageMath.Withdraw(ledger, inId, inAmount * runs);
else
MachineSlotMath.Withdraw(input, inId, inAmount * runs);
StorageMath.Deposit(ledger, (ushort)fab.ValueRO.OutResourceId, fab.ValueRO.OutAmount * runs);
}
// Re-stamp every due period regardless of runs (starved fabricators re-evaluate next period, not
// every tick) so the catch-up baseline never silently rewinds.
uint p = (uint)System.Math.Max(1, period);
ps.ValueRW.LastProcessedTick = TickUtil.NonZero(now);
ps.ValueRW.NextTick = TickUtil.NonZero(now + p);
}
}
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 1d8dbf02b41c9a94ea57fd7ca00f266d
@@ -1,76 +0,0 @@
using ProjectM.Simulation;
using Unity.Burst;
using Unity.Entities;
using Unity.NetCode;
namespace ProjectM.Server
{
/// <summary>
/// Server-only, deterministic harvester production — the FRONT of the M7 auto-gather chain
/// (Harvester → Conveyor → Fabricator). Each <see cref="Harvester"/> machine is a fixed-yield generator:
/// every <see cref="Harvester.PeriodTicks"/> server ticks it deposits <see cref="Harvester.Yield"/> of its
/// configured (byte) resource into its OWN server-only <see cref="MachineOutput"/> buffer (NOT the global
/// ledger — a conveyor pulls it onward, or it sits buffered). Mirrors <c>TurretFireSystem</c>'s exact
/// now-extraction (<c>NetworkTime.ServerTick.TickIndexForValidTick</c>) + <see cref="PlacedStructure.NextTick"/>
/// cooldown idiom, and runs in the plain server <c>SimulationSystemGroup</c>
/// <c>[UpdateAfter(PredictedSimulationSystemGroup)]</c> (the predicted group is OrderFirst → UpdateBefore is
/// ignored). Production mutates a DynamicBuffer in place (not a structural change) → no ECB needed.
/// <para>
/// SINGLE GATED CATCH-UP PATH (offline-quit safe, NO wall-clock minting): a never-processed machine
/// (LastProcessedTick==0) is initialised this tick and produces nothing; otherwise
/// <see cref="ProductionMath.CyclesDue"/> awards <c>floor((now-LastProcessedTick)/period)</c> cycles, clamped
/// to <see cref="Tuning.MaxProductionCatchup"/>, and the tick fields are re-stamped. All catch-up is
/// WITHIN-SESSION tick math; the stockpile is preserved across quit by the persistence layer, never re-minted
/// from a saved wall-clock.
/// </para>
/// </summary>
[BurstCompile]
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
[UpdateInGroup(typeof(SimulationSystemGroup))]
[UpdateAfter(typeof(PredictedSimulationSystemGroup))]
public partial struct HarvesterProductionSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<NetworkTime>();
state.RequireForUpdate(state.GetEntityQuery(ComponentType.ReadOnly<Harvester>()));
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
var serverTick = SystemAPI.GetSingleton<NetworkTime>().ServerTick;
if (!serverTick.IsValid)
return;
uint now = serverTick.TickIndexForValidTick;
foreach (var (ps, harvester, output) in
SystemAPI.Query<RefRW<PlacedStructure>, RefRO<Harvester>, DynamicBuffer<MachineOutput>>())
{
int period = harvester.ValueRO.PeriodTicks; // CyclesDue clamps to max(1, period)
// Never-processed (baked/just-placed) machine: initialise the catch-up baseline, produce nothing.
if (ProductionMath.NeedsInit(ps.ValueRO.LastProcessedTick))
{
ps.ValueRW.LastProcessedTick = TickUtil.NonZero(now);
ps.ValueRW.NextTick = TickUtil.NonZero(now + (uint)System.Math.Max(1, period));
continue;
}
int cycles = ProductionMath.CyclesDue(
serverTick, ps.ValueRO.NextTick, ps.ValueRO.LastProcessedTick, period, Tuning.MaxProductionCatchup);
if (cycles <= 0)
continue; // still cooling down / nothing due
// Fixed-yield generation into the machine's own output slot (byte id; ledger conversion happens
// only at the global-ledger boundary, which this machine never crosses directly).
MachineSlotMath.Deposit(output, harvester.ValueRO.ResourceId, harvester.ValueRO.Yield * cycles);
uint p = (uint)System.Math.Max(1, period);
ps.ValueRW.LastProcessedTick = TickUtil.NonZero(now);
ps.ValueRW.NextTick = TickUtil.NonZero(now + p);
}
}
}
}
@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: c484cb2331137134888f10eed7689140
@@ -23,13 +23,11 @@ namespace ProjectM.Server
public partial struct BuildPlaceSystem : ISystem
{
ComponentLookup<LocalTransform> m_TransformLookup;
ComponentLookup<Conveyor> m_ConveyorLookup;
[BurstCompile]
public void OnCreate(ref SystemState state)
{
m_TransformLookup = state.GetComponentLookup<LocalTransform>(isReadOnly: true);
m_ConveyorLookup = state.GetComponentLookup<Conveyor>(isReadOnly: true);
state.RequireForUpdate<StructureCatalog>();
state.RequireForUpdate<BaseAnchor>();
state.RequireForUpdate<ResourceLedger>();
@@ -43,21 +41,16 @@ namespace ProjectM.Server
public void OnUpdate(ref SystemState state)
{
m_TransformLookup.Update(ref state);
m_ConveyorLookup.Update(ref state);
uint now = SystemAPI.GetSingleton<NetworkTime>().ServerTick.TickIndexForValidTick;
var anchor = SystemAPI.GetSingleton<BaseAnchor>();
var catalog = SystemAPI.GetBuffer<StructureCatalogEntry>(SystemAPI.GetSingletonEntity<StructureCatalog>());
var ledger = SystemAPI.GetBuffer<StorageEntry>(SystemAPI.GetSingletonEntity<ResourceLedger>());
// Derive occupancy from the live structure set (authoritative); also count turrets for the per-base cap.
// Derive occupancy from the live structure set (authoritative).
var occupied = new NativeHashSet<int2>(64, Allocator.Temp);
int turretCount = 0;
foreach (var ps in SystemAPI.Query<RefRO<PlacedStructure>>())
{
occupied.Add(ps.ValueRO.Cell);
if (ps.ValueRO.Type == StructureType.Turret) turretCount++;
}
var ecb = new EntityCommandBuffer(Allocator.Temp);
@@ -71,9 +64,7 @@ namespace ProjectM.Server
for (int i = 0; i < catalog.Length; i++)
if (catalog[i].Type == req.StructureType) { entryIdx = i; break; }
// DR-042 combat pass: cap turrets per base (server-authoritative) so they can't be spammed.
bool turretCapOk = req.StructureType != StructureType.Turret || turretCount < Tuning.TurretCap;
if (entryIdx >= 0 && catalog[entryIdx].Prefab != Entity.Null && turretCapOk
if (entryIdx >= 0 && catalog[entryIdx].Prefab != Entity.Null
&& BuildPlacementMath.CanPlace(anchor, occupied, cell))
{
var entry = catalog[entryIdx];
@@ -87,7 +78,6 @@ namespace ProjectM.Server
// Commit IN-PLACE so a second same-tick request sees the spend + reservation.
StorageMath.Withdraw(ledger, entry.CostResourceId, entry.CostAmount);
occupied.Add(cell);
if (req.StructureType == StructureType.Turret) turretCount++; // keep same-tick turret requests under the cap
var structure = ecb.Instantiate(entry.Prefab);
var xform = m_TransformLookup[entry.Prefab];
@@ -102,12 +92,6 @@ namespace ProjectM.Server
});
ecb.AddComponent(structure, new RegionTag { Region = RegionId.Base });
ecb.AddComponent<RuntimePlacedTag>(structure); // player-built -> persisted by SaveStructureScan
if (req.StructureType == StructureType.Conveyor && m_ConveyorLookup.HasComponent(entry.Prefab))
{
var conv = m_ConveyorLookup[entry.Prefab];
conv.Direction = req.Direction;
ecb.SetComponent(structure, conv);
}
}
}

Some files were not shown because too many files have changed in this diff Show More