Compare commits

..

No commits in common. "3612d00cdc934a94822471f577550c48fcafb02b" and "3c44aaf2bd62a69db3809fdb37e9ec5d141eb5e0" have entirely different histories.

72 changed files with 30398 additions and 18456 deletions

4
.gitignore vendored
View File

@ -32,7 +32,3 @@ sysinfo.txt
Assets/Plugins.meta
Assets/Plugins/**
.venv
AGENTS.md
.python-version

View File

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: be54b69524c434f888d7c380a558ccee
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,613 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !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: fcb661d795ed4088af4ae73e4253f98b, type: 3}
m_Name: JGraph
m_EditorClassIdentifier: Assembly-CSharp::Intrepid.Gameplay.Journey.JGraphSO
sourceWorldLayoutId: 858671c3949e4543b0d68818c9b2e41b
publishedAt: 2026-06-20T07:40:11.0297990Z
schemaVersion: 1
contentHash: 0c6d1b68e944d050c332427770faa770e77fac44b9fc92372e5b1487ff21331c
stages:
- id: stage_9202fe485eaa408d86e379b3fdafa49b
displayName: Stage 1
worldLayer: 0
occupiedTiles: []
tags: []
- id: stage_25e8797ce5d9420b97aaf3e06f760b1d
displayName: Stage 2
worldLayer: 0
occupiedTiles: []
tags: []
- id: stage_f5a0fa8cc3a14a94b8b6fa80c4e2413d
displayName: Stage 3
worldLayer: 0
occupiedTiles:
- x: 8
y: 8
- x: 9
y: 8
- x: 10
y: 8
- x: 11
y: 8
- x: 12
y: 8
- x: 8
y: 9
- x: 9
y: 9
- x: 10
y: 9
- x: 11
y: 9
- x: 12
y: 9
- x: 8
y: 10
- x: 9
y: 10
- x: 10
y: 10
- x: 11
y: 10
- x: 12
y: 10
- x: 8
y: 11
- x: 9
y: 11
- x: 10
y: 11
- x: 11
y: 11
- x: 12
y: 11
- x: 8
y: 12
- x: 9
y: 12
- x: 10
y: 12
- x: 11
y: 12
- x: 12
y: 12
- x: 13
y: 9
- x: 13
y: 10
- x: 13
y: 11
- x: 13
y: 12
- x: 8
y: 7
- x: 9
y: 7
- x: 10
y: 7
- x: 11
y: 7
- x: 7
y: 7
- x: 7
y: 8
- x: 7
y: 9
- x: 7
y: 10
tags: []
- id: stage_92bb328658e04aceb915822ac7ff20ae
displayName: Stage 4
worldLayer: 0
occupiedTiles: []
tags: []
- id: stage_aae8c48d41d84a679c9c52acd567ff30
displayName: Stage 5
worldLayer: 0
occupiedTiles:
- x: 8
y: 13
- x: 9
y: 13
- x: 10
y: 13
- x: 11
y: 13
- x: 12
y: 13
- x: 8
y: 14
- x: 9
y: 14
- x: 10
y: 14
- x: 11
y: 14
- x: 12
y: 14
- x: 8
y: 15
- x: 9
y: 15
- x: 10
y: 15
- x: 11
y: 15
- x: 12
y: 15
- x: 8
y: 16
- x: 9
y: 16
- x: 10
y: 16
- x: 11
y: 16
- x: 12
y: 16
- x: 13
y: 14
- x: 13
y: 15
- x: 13
y: 16
- x: 11
y: 17
- x: 12
y: 17
- x: 13
y: 17
tags: []
- id: stage_526b633d818b4e36b96ad92f540b9415
displayName: Stage 6
worldLayer: 0
occupiedTiles:
- x: 17
y: 7
- x: 18
y: 7
- x: 19
y: 7
- x: 17
y: 8
- x: 18
y: 8
- x: 19
y: 8
tags: []
- id: stage_3e76ac8acd0447d59b0fbf1a65a25fb3
displayName: Stage 7
worldLayer: 0
occupiedTiles:
- x: 13
y: 13
- x: 14
y: 13
- x: 16
y: 13
- x: 17
y: 13
- x: 15
y: 13
- x: 14
y: 9
- x: 15
y: 9
- x: 16
y: 9
- x: 17
y: 9
- x: 18
y: 9
- x: 19
y: 9
- x: 14
y: 10
- x: 15
y: 10
- x: 16
y: 10
- x: 17
y: 10
- x: 18
y: 10
- x: 19
y: 10
- x: 14
y: 11
- x: 15
y: 11
- x: 16
y: 11
- x: 17
y: 11
- x: 18
y: 11
- x: 19
y: 11
- x: 14
y: 12
- x: 15
y: 12
- x: 16
y: 12
- x: 17
y: 12
- x: 18
y: 12
- x: 19
y: 12
- x: 18
y: 13
- x: 19
y: 13
- x: 14
y: 14
- x: 15
y: 14
- x: 16
y: 14
- x: 17
y: 14
- x: 18
y: 14
- x: 19
y: 14
- x: 14
y: 15
- x: 15
y: 15
- x: 16
y: 15
- x: 17
y: 15
- x: 18
y: 15
- x: 19
y: 15
- x: 14
y: 16
- x: 15
y: 16
- x: 16
y: 16
- x: 17
y: 16
- x: 18
y: 16
- x: 19
y: 16
- x: 14
y: 17
- x: 15
y: 17
- x: 16
y: 17
- x: 17
y: 17
- x: 18
y: 17
- x: 19
y: 17
tags: []
- id: stage_1a60b23d8b4c4622905831237c716081
displayName: Stage 8
worldLayer: 1
occupiedTiles:
- x: 16
y: 8
- x: 17
y: 8
- x: 18
y: 8
- x: 19
y: 8
- x: 20
y: 8
- x: 16
y: 9
- x: 17
y: 9
- x: 18
y: 9
- x: 19
y: 9
- x: 20
y: 9
- x: 16
y: 10
- x: 17
y: 10
- x: 18
y: 10
- x: 19
y: 10
- x: 20
y: 10
- x: 16
y: 11
- x: 17
y: 11
- x: 18
y: 11
- x: 19
y: 11
- x: 20
y: 11
- x: 16
y: 12
- x: 17
y: 12
- x: 18
y: 12
- x: 19
y: 12
- x: 20
y: 12
tags: []
- id: stage_a980c91d39bc470296289d3ef72636c0
displayName: Stage 9
worldLayer: -1
occupiedTiles:
- x: 17
y: 8
- x: 18
y: 8
- x: 19
y: 8
- x: 17
y: 9
- x: 18
y: 9
- x: 19
y: 9
- x: 17
y: 10
- x: 18
y: 10
- x: 19
y: 10
- x: 17
y: 11
- x: 18
y: 11
- x: 19
y: 11
tags: []
- id: stage_78ca4be5151f4c60818dce81558ab162
displayName: Stage 10
worldLayer: -2
occupiedTiles:
- x: 18
y: 10
- x: 16
y: 8
- x: 17
y: 8
- x: 18
y: 8
- x: 19
y: 8
- x: 20
y: 8
- x: 16
y: 9
- x: 17
y: 9
- x: 18
y: 9
- x: 19
y: 9
- x: 20
y: 9
- x: 16
y: 10
- x: 17
y: 10
- x: 19
y: 10
- x: 20
y: 10
- x: 16
y: 11
- x: 17
y: 11
- x: 18
y: 11
- x: 19
y: 11
- x: 20
y: 11
- x: 16
y: 12
- x: 17
y: 12
- x: 18
y: 12
- x: 19
y: 12
- x: 20
y: 12
tags: []
- id: stage_bfa43a65aced42ceb03f56b11dce0998
displayName: Stage 15
worldLayer: 0
occupiedTiles: []
tags: []
- id: stage_b24d4774f45444138b8cfda33ed5034e
displayName: Stage 16
worldLayer: 0
occupiedTiles: []
tags: []
endpoints:
- id: endpoint_adc34044a48447d0be41389ba398d5f5
stageId: stage_aae8c48d41d84a679c9c52acd567ff30
worldTile:
x: 10
y: 13
kind: 0
required: 1
socketFace: 0
- id: endpoint_181357103f5742008cfa73a544a56363
stageId: stage_f5a0fa8cc3a14a94b8b6fa80c4e2413d
worldTile:
x: 10
y: 12
kind: 0
required: 1
socketFace: 1
- id: endpoint_71bd3851ad2b4d20a90cf3b3f5ade5fb
stageId: stage_3e76ac8acd0447d59b0fbf1a65a25fb3
worldTile:
x: 13
y: 13
kind: 0
required: 1
socketFace: 1
- id: endpoint_d9121bcc789443548c898106dd5f4c43
stageId: stage_aae8c48d41d84a679c9c52acd567ff30
worldTile:
x: 13
y: 14
kind: 0
required: 1
socketFace: 0
- id: endpoint_dc223745e7994fbd880f1e93f146d8cf
stageId: stage_3e76ac8acd0447d59b0fbf1a65a25fb3
worldTile:
x: 13
y: 13
kind: 0
required: 1
socketFace: 0
- id: endpoint_65e01fe22fe44222919c322ea8580a69
stageId: stage_f5a0fa8cc3a14a94b8b6fa80c4e2413d
worldTile:
x: 13
y: 12
kind: 0
required: 1
socketFace: 1
- id: endpoint_27a37615b51f40cdb39acfc9fbffce22
stageId: stage_526b633d818b4e36b96ad92f540b9415
worldTile:
x: 18
y: 8
kind: 0
required: 1
socketFace: 1
- id: endpoint_7cf95989e98a4de49edb7e2080e1c5e6
stageId: stage_3e76ac8acd0447d59b0fbf1a65a25fb3
worldTile:
x: 18
y: 9
kind: 0
required: 1
socketFace: 0
- id: endpoint_34e27be82570488cbcd45531e56f128c
stageId: stage_f5a0fa8cc3a14a94b8b6fa80c4e2413d
worldTile:
x: 10
y: 10
kind: 1
required: 1
socketFace: 0
- id: endpoint_e6e40c3366564b96825ecf652721a46d
stageId: stage_3e76ac8acd0447d59b0fbf1a65a25fb3
worldTile:
x: 17
y: 13
kind: 1
required: 1
socketFace: 0
- id: endpoint_9c245891f5734102bc24edb0dcc2c9fd
stageId: stage_aae8c48d41d84a679c9c52acd567ff30
worldTile:
x: 11
y: 15
kind: 1
required: 1
socketFace: 0
- id: endpoint_0e3b0c016a474adb8fe8b05bdfd79f14
stageId: stage_3e76ac8acd0447d59b0fbf1a65a25fb3
worldTile:
x: 18
y: 10
kind: 2
required: 1
socketFace: 0
- id: endpoint_6f17b532985e4e908e70b3802eccf8cc
stageId: stage_78ca4be5151f4c60818dce81558ab162
worldTile:
x: 18
y: 10
kind: 2
required: 1
socketFace: 0
- id: endpoint_ae66297a1c1a4c259e26ea8b51d69851
stageId: stage_1a60b23d8b4c4622905831237c716081
worldTile:
x: 18
y: 10
kind: 2
required: 1
socketFace: 0
- id: endpoint_263ea59d79614b9b85ed2bb4fe25c9ad
stageId: stage_a980c91d39bc470296289d3ef72636c0
worldTile:
x: 18
y: 10
kind: 2
required: 1
socketFace: 0
connections:
- id: connection_cdf62cf06a20426b88d3987ea8917967
endpointAId: endpoint_adc34044a48447d0be41389ba398d5f5
endpointBId: endpoint_181357103f5742008cfa73a544a56363
directionality: 0
- id: connection_3dabe3efe429439bbb49104bed37bb20
endpointAId: endpoint_71bd3851ad2b4d20a90cf3b3f5ade5fb
endpointBId: endpoint_d9121bcc789443548c898106dd5f4c43
directionality: 0
- id: connection_ef34c049437c491b80364a034880300d
endpointAId: endpoint_dc223745e7994fbd880f1e93f146d8cf
endpointBId: endpoint_65e01fe22fe44222919c322ea8580a69
directionality: 0
- id: connection_2b7aa20f26bd48acb636a37ed1d20ad0
endpointAId: endpoint_27a37615b51f40cdb39acfc9fbffce22
endpointBId: endpoint_7cf95989e98a4de49edb7e2080e1c5e6
directionality: 0
- id: connection_d8da15ab563d4bb9ba6f0294cec60341
endpointAId: endpoint_e6e40c3366564b96825ecf652721a46d
endpointBId: endpoint_34e27be82570488cbcd45531e56f128c
directionality: 0
- id: connection_335b1c8a1f964b11b72ddf2ef053bd71
endpointAId: endpoint_9c245891f5734102bc24edb0dcc2c9fd
endpointBId: endpoint_e6e40c3366564b96825ecf652721a46d
directionality: 0
- id: connection_d0e5d7611be64e31a77bc6d24650a031
endpointAId: endpoint_9c245891f5734102bc24edb0dcc2c9fd
endpointBId: endpoint_34e27be82570488cbcd45531e56f128c
directionality: 0
- id: connection_12db74114bf54c8eb5b7038efcae4172
endpointAId: endpoint_ae66297a1c1a4c259e26ea8b51d69851
endpointBId: endpoint_0e3b0c016a474adb8fe8b05bdfd79f14
directionality: 0
- id: connection_274434096af24703a063627df527d688
endpointAId: endpoint_263ea59d79614b9b85ed2bb4fe25c9ad
endpointBId: endpoint_0e3b0c016a474adb8fe8b05bdfd79f14
directionality: 0
- id: connection_22f10261be3b4a24ada8debf789f9e33
endpointAId: endpoint_263ea59d79614b9b85ed2bb4fe25c9ad
endpointBId: endpoint_6f17b532985e4e908e70b3802eccf8cc
directionality: 0

View File

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 0e8c3fc507c9b42e8b1f2df5009b5937
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,638 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !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: 8cbbff7dfcc24d2b9030f41f9f2ef718, type: 3}
m_Name: WorldLayout
m_EditorClassIdentifier: Assembly-CSharp-Editor::Editor.GON.WorldEditor.WorldLayoutSO
id: 858671c3949e4543b0d68818c9b2e41b
worldTileSize: 24
worldLayerHeight: 24
minWorldLayer: -2
maxWorldLayer: 1
stages:
- id: stage_9202fe485eaa408d86e379b3fdafa49b
displayName: Stage 1
stageKind: 0
worldLayer: 0
color: {r: 0.43435237, g: 0.72672, b: 0.73008716, a: 0.820845}
stageDefinition: {fileID: 0}
occupiedTiles: []
- id: stage_25e8797ce5d9420b97aaf3e06f760b1d
displayName: Stage 2
stageKind: 0
worldLayer: 0
color: {r: 0.71619093, g: 0.6632371, b: 0.3653053, a: 0.7631802}
stageDefinition: {fileID: 0}
occupiedTiles: []
- id: stage_f5a0fa8cc3a14a94b8b6fa80c4e2413d
displayName: Stage 3
stageKind: 0
worldLayer: 0
color: {r: 0.26295125, g: 0.36055228, b: 0.6102875, a: 0.76864}
stageDefinition: {fileID: 0}
occupiedTiles:
- x: 8
y: 8
- x: 9
y: 8
- x: 10
y: 8
- x: 11
y: 8
- x: 12
y: 8
- x: 8
y: 9
- x: 9
y: 9
- x: 10
y: 9
- x: 11
y: 9
- x: 12
y: 9
- x: 8
y: 10
- x: 9
y: 10
- x: 10
y: 10
- x: 11
y: 10
- x: 12
y: 10
- x: 8
y: 11
- x: 9
y: 11
- x: 10
y: 11
- x: 11
y: 11
- x: 12
y: 11
- x: 8
y: 12
- x: 9
y: 12
- x: 10
y: 12
- x: 11
y: 12
- x: 12
y: 12
- x: 13
y: 9
- x: 13
y: 10
- x: 13
y: 11
- x: 13
y: 12
- x: 8
y: 7
- x: 9
y: 7
- x: 10
y: 7
- x: 11
y: 7
- x: 7
y: 7
- x: 7
y: 8
- x: 7
y: 9
- x: 7
y: 10
- id: stage_92bb328658e04aceb915822ac7ff20ae
displayName: Stage 4
stageKind: 0
worldLayer: 0
color: {r: 0.7052704, g: 0.6072819, b: 0.39938506, a: 0.8043456}
stageDefinition: {fileID: 0}
occupiedTiles: []
- id: stage_aae8c48d41d84a679c9c52acd567ff30
displayName: Stage 5
stageKind: 0
worldLayer: 0
color: {r: 0.73909104, g: 0.47607818, b: 0.6006857, a: 0.8447911}
stageDefinition: {fileID: 0}
occupiedTiles:
- x: 8
y: 13
- x: 9
y: 13
- x: 10
y: 13
- x: 11
y: 13
- x: 12
y: 13
- x: 8
y: 14
- x: 9
y: 14
- x: 10
y: 14
- x: 11
y: 14
- x: 12
y: 14
- x: 8
y: 15
- x: 9
y: 15
- x: 10
y: 15
- x: 11
y: 15
- x: 12
y: 15
- x: 8
y: 16
- x: 9
y: 16
- x: 10
y: 16
- x: 11
y: 16
- x: 12
y: 16
- x: 13
y: 14
- x: 13
y: 15
- x: 13
y: 16
- x: 11
y: 17
- x: 12
y: 17
- x: 13
y: 17
- id: stage_526b633d818b4e36b96ad92f540b9415
displayName: Stage 6
stageKind: 0
worldLayer: 0
color: {r: 0.59988964, g: 0.84526074, b: 0.5487183, a: 0.7954139}
stageDefinition: {fileID: 0}
occupiedTiles:
- x: 17
y: 7
- x: 18
y: 7
- x: 19
y: 7
- x: 17
y: 8
- x: 18
y: 8
- x: 19
y: 8
- id: stage_3e76ac8acd0447d59b0fbf1a65a25fb3
displayName: Stage 7
stageKind: 0
worldLayer: 0
color: {r: 0.7086307, g: 0.25315043, b: 0.84043574, a: 0.79866225}
stageDefinition: {fileID: 0}
occupiedTiles:
- x: 13
y: 13
- x: 14
y: 13
- x: 16
y: 13
- x: 17
y: 13
- x: 15
y: 13
- x: 14
y: 9
- x: 15
y: 9
- x: 16
y: 9
- x: 17
y: 9
- x: 18
y: 9
- x: 19
y: 9
- x: 14
y: 10
- x: 15
y: 10
- x: 16
y: 10
- x: 17
y: 10
- x: 18
y: 10
- x: 19
y: 10
- x: 14
y: 11
- x: 15
y: 11
- x: 16
y: 11
- x: 17
y: 11
- x: 18
y: 11
- x: 19
y: 11
- x: 14
y: 12
- x: 15
y: 12
- x: 16
y: 12
- x: 17
y: 12
- x: 18
y: 12
- x: 19
y: 12
- x: 18
y: 13
- x: 19
y: 13
- x: 14
y: 14
- x: 15
y: 14
- x: 16
y: 14
- x: 17
y: 14
- x: 18
y: 14
- x: 19
y: 14
- x: 14
y: 15
- x: 15
y: 15
- x: 16
y: 15
- x: 17
y: 15
- x: 18
y: 15
- x: 19
y: 15
- x: 14
y: 16
- x: 15
y: 16
- x: 16
y: 16
- x: 17
y: 16
- x: 18
y: 16
- x: 19
y: 16
- x: 14
y: 17
- x: 15
y: 17
- x: 16
y: 17
- x: 17
y: 17
- x: 18
y: 17
- x: 19
y: 17
- id: stage_1a60b23d8b4c4622905831237c716081
displayName: Stage 8
stageKind: 0
worldLayer: 1
color: {r: 0.41402355, g: 0.6012687, b: 0.37944123, a: 0.8586461}
stageDefinition: {fileID: 0}
occupiedTiles:
- x: 16
y: 8
- x: 17
y: 8
- x: 18
y: 8
- x: 19
y: 8
- x: 20
y: 8
- x: 16
y: 9
- x: 17
y: 9
- x: 18
y: 9
- x: 19
y: 9
- x: 20
y: 9
- x: 16
y: 10
- x: 17
y: 10
- x: 18
y: 10
- x: 19
y: 10
- x: 20
y: 10
- x: 16
y: 11
- x: 17
y: 11
- x: 18
y: 11
- x: 19
y: 11
- x: 20
y: 11
- x: 16
y: 12
- x: 17
y: 12
- x: 18
y: 12
- x: 19
y: 12
- x: 20
y: 12
- id: stage_a980c91d39bc470296289d3ef72636c0
displayName: Stage 9
stageKind: 0
worldLayer: -1
color: {r: 0.6987197, g: 0.5302855, b: 0.419144, a: 0.76745874}
stageDefinition: {fileID: 0}
occupiedTiles:
- x: 17
y: 8
- x: 18
y: 8
- x: 19
y: 8
- x: 17
y: 9
- x: 18
y: 9
- x: 19
y: 9
- x: 17
y: 10
- x: 18
y: 10
- x: 19
y: 10
- x: 17
y: 11
- x: 18
y: 11
- x: 19
y: 11
- id: stage_78ca4be5151f4c60818dce81558ab162
displayName: Stage 10
stageKind: 0
worldLayer: -2
color: {r: 0.58324605, g: 0.27982995, b: 0.5501635, a: 0.85375965}
stageDefinition: {fileID: 0}
occupiedTiles:
- x: 18
y: 10
- x: 16
y: 8
- x: 17
y: 8
- x: 18
y: 8
- x: 19
y: 8
- x: 20
y: 8
- x: 16
y: 9
- x: 17
y: 9
- x: 18
y: 9
- x: 19
y: 9
- x: 20
y: 9
- x: 16
y: 10
- x: 17
y: 10
- x: 19
y: 10
- x: 20
y: 10
- x: 16
y: 11
- x: 17
y: 11
- x: 18
y: 11
- x: 19
y: 11
- x: 20
y: 11
- x: 16
y: 12
- x: 17
y: 12
- x: 18
y: 12
- x: 19
y: 12
- x: 20
y: 12
- id: stage_bfa43a65aced42ceb03f56b11dce0998
displayName: Stage 15
stageKind: 0
worldLayer: 0
color: {r: 0.82491213, g: 0.683962, b: 0.2155831, a: 0.8341453}
stageDefinition: {fileID: 0}
occupiedTiles: []
- id: stage_b24d4774f45444138b8cfda33ed5034e
displayName: Stage 16
stageKind: 0
worldLayer: 0
color: {r: 0.44949582, g: 0.6144462, b: 0.7657885, a: 0.82364887}
stageDefinition: {fileID: 0}
occupiedTiles: []
endpoints:
- id: endpoint_adc34044a48447d0be41389ba398d5f5
stageId: stage_aae8c48d41d84a679c9c52acd567ff30
worldTile:
x: 10
y: 13
kind: 0
required: 1
socketFace: 0
- id: endpoint_181357103f5742008cfa73a544a56363
stageId: stage_f5a0fa8cc3a14a94b8b6fa80c4e2413d
worldTile:
x: 10
y: 12
kind: 0
required: 1
socketFace: 1
- id: endpoint_71bd3851ad2b4d20a90cf3b3f5ade5fb
stageId: stage_3e76ac8acd0447d59b0fbf1a65a25fb3
worldTile:
x: 13
y: 13
kind: 0
required: 1
socketFace: 1
- id: endpoint_d9121bcc789443548c898106dd5f4c43
stageId: stage_aae8c48d41d84a679c9c52acd567ff30
worldTile:
x: 13
y: 14
kind: 0
required: 1
socketFace: 0
- id: endpoint_dc223745e7994fbd880f1e93f146d8cf
stageId: stage_3e76ac8acd0447d59b0fbf1a65a25fb3
worldTile:
x: 13
y: 13
kind: 0
required: 1
socketFace: 0
- id: endpoint_65e01fe22fe44222919c322ea8580a69
stageId: stage_f5a0fa8cc3a14a94b8b6fa80c4e2413d
worldTile:
x: 13
y: 12
kind: 0
required: 1
socketFace: 1
- id: endpoint_27a37615b51f40cdb39acfc9fbffce22
stageId: stage_526b633d818b4e36b96ad92f540b9415
worldTile:
x: 18
y: 8
kind: 0
required: 1
socketFace: 1
- id: endpoint_7cf95989e98a4de49edb7e2080e1c5e6
stageId: stage_3e76ac8acd0447d59b0fbf1a65a25fb3
worldTile:
x: 18
y: 9
kind: 0
required: 1
socketFace: 0
- id: endpoint_34e27be82570488cbcd45531e56f128c
stageId: stage_f5a0fa8cc3a14a94b8b6fa80c4e2413d
worldTile:
x: 10
y: 10
kind: 1
required: 1
socketFace: 0
- id: endpoint_e6e40c3366564b96825ecf652721a46d
stageId: stage_3e76ac8acd0447d59b0fbf1a65a25fb3
worldTile:
x: 17
y: 13
kind: 1
required: 1
socketFace: 0
- id: endpoint_9c245891f5734102bc24edb0dcc2c9fd
stageId: stage_aae8c48d41d84a679c9c52acd567ff30
worldTile:
x: 11
y: 15
kind: 1
required: 1
socketFace: 0
- id: endpoint_0e3b0c016a474adb8fe8b05bdfd79f14
stageId: stage_3e76ac8acd0447d59b0fbf1a65a25fb3
worldTile:
x: 18
y: 10
kind: 2
required: 1
socketFace: 0
- id: endpoint_6f17b532985e4e908e70b3802eccf8cc
stageId: stage_78ca4be5151f4c60818dce81558ab162
worldTile:
x: 18
y: 10
kind: 2
required: 1
socketFace: 0
- id: endpoint_ae66297a1c1a4c259e26ea8b51d69851
stageId: stage_1a60b23d8b4c4622905831237c716081
worldTile:
x: 18
y: 10
kind: 2
required: 1
socketFace: 0
- id: endpoint_263ea59d79614b9b85ed2bb4fe25c9ad
stageId: stage_a980c91d39bc470296289d3ef72636c0
worldTile:
x: 18
y: 10
kind: 2
required: 1
socketFace: 0
connections:
- id: connection_cdf62cf06a20426b88d3987ea8917967
endpointAId: endpoint_adc34044a48447d0be41389ba398d5f5
endpointBId: endpoint_181357103f5742008cfa73a544a56363
directionality: 0
- id: connection_3dabe3efe429439bbb49104bed37bb20
endpointAId: endpoint_71bd3851ad2b4d20a90cf3b3f5ade5fb
endpointBId: endpoint_d9121bcc789443548c898106dd5f4c43
directionality: 0
- id: connection_ef34c049437c491b80364a034880300d
endpointAId: endpoint_dc223745e7994fbd880f1e93f146d8cf
endpointBId: endpoint_65e01fe22fe44222919c322ea8580a69
directionality: 0
- id: connection_2b7aa20f26bd48acb636a37ed1d20ad0
endpointAId: endpoint_27a37615b51f40cdb39acfc9fbffce22
endpointBId: endpoint_7cf95989e98a4de49edb7e2080e1c5e6
directionality: 0
- id: connection_d8da15ab563d4bb9ba6f0294cec60341
endpointAId: endpoint_e6e40c3366564b96825ecf652721a46d
endpointBId: endpoint_34e27be82570488cbcd45531e56f128c
directionality: 0
- id: connection_335b1c8a1f964b11b72ddf2ef053bd71
endpointAId: endpoint_9c245891f5734102bc24edb0dcc2c9fd
endpointBId: endpoint_e6e40c3366564b96825ecf652721a46d
directionality: 0
- id: connection_d0e5d7611be64e31a77bc6d24650a031
endpointAId: endpoint_9c245891f5734102bc24edb0dcc2c9fd
endpointBId: endpoint_34e27be82570488cbcd45531e56f128c
directionality: 0
- id: connection_12db74114bf54c8eb5b7038efcae4172
endpointAId: endpoint_ae66297a1c1a4c259e26ea8b51d69851
endpointBId: endpoint_0e3b0c016a474adb8fe8b05bdfd79f14
directionality: 0
- id: connection_274434096af24703a063627df527d688
endpointAId: endpoint_263ea59d79614b9b85ed2bb4fe25c9ad
endpointBId: endpoint_0e3b0c016a474adb8fe8b05bdfd79f14
directionality: 0
- id: connection_22f10261be3b4a24ada8debf789f9e33
endpointAId: endpoint_263ea59d79614b9b85ed2bb4fe25c9ad
endpointBId: endpoint_6f17b532985e4e908e70b3802eccf8cc
directionality: 0

View File

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 8ec2b6c87674e4b27863ffb69c081b90
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because one or more lines are too long

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: f13036ba22b24a8c93b188ae40c108a5
timeCreated: 1781871128

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: a415e0c7f0ff4b95af8ae97c871a84ef
timeCreated: 1781871135

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 450f645e91eb4e529ce4f7337f11ca15
timeCreated: 1781871512

View File

@ -1,160 +0,0 @@
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using Intrepid.Gameplay.Journey;
using UnityEditor;
namespace Editor.GON.WorldEditor
{
public static class JourneyCommitter
{
public static List<WorldValidationMessage> Commit(WorldLayoutSO layout, JGraphSO graph)
{
var messages = WorldLayoutValidator.Validate(layout);
if (WorldLayoutValidator.HasErrors(messages) || graph is null)
{
if (graph is null)
{
messages.Add(new WorldValidationMessage(WorldValidationSeverity.Error, "No target JGraphSO selected."));
}
return messages;
}
layout.EnsureDefaults();
graph.sourceWorldLayoutId = layout.id;
graph.publishedAt = DateTime.UtcNow.ToString("O");
graph.schemaVersion = 1;
graph.stages = BuildStages(layout);
graph.endpoints = BuildEndpoints(layout);
graph.connections = BuildConnections(layout);
graph.contentHash = ComputeContentHash(graph);
graph.EnsureDefaults();
EditorUtility.SetDirty(graph);
AssetDatabase.SaveAssets();
return messages;
}
private static List<JStage> BuildStages(WorldLayoutSO layout)
{
var stages = new List<JStage>();
foreach (var source in layout.stages)
{
if (source is null)
{
continue;
}
source.EnsureDefaults();
var stage = new JStage
{
id = source.id,
displayName = source.displayName,
worldLayer = source.worldLayer,
occupiedTiles = new List<JWorldTile>(source.occupiedTiles)
};
if (source.stageKind == WorldStageKind.SavePoint)
{
stage.tags.Add(WorldLayoutSO.SavePointTag);
}
stages.Add(stage);
}
return stages;
}
private static List<JEndpoint> BuildEndpoints(WorldLayoutSO layout)
{
var endpoints = new List<JEndpoint>();
foreach (var source in layout.endpoints)
{
if (source is null)
{
continue;
}
endpoints.Add(new JEndpoint
{
id = source.id,
stageId = source.stageId,
worldTile = source.worldTile,
kind = source.kind,
required = source.required,
socketFace = source.socketFace
});
}
return endpoints;
}
private static List<JConnection> BuildConnections(WorldLayoutSO layout)
{
var connections = new List<JConnection>();
foreach (var source in layout.connections)
{
if (source is null)
{
continue;
}
connections.Add(new JConnection
{
id = source.id,
endpointAId = source.endpointAId,
endpointBId = source.endpointBId,
directionality = source.directionality
});
}
return connections;
}
private static string ComputeContentHash(JGraphSO graph)
{
var builder = new StringBuilder();
builder.Append(graph.sourceWorldLayoutId).Append('|').Append(graph.schemaVersion).AppendLine();
foreach (var stage in graph.stages)
{
builder.Append("S|").Append(stage.id).Append('|').Append(stage.displayName).Append('|')
.Append(stage.worldLayer).AppendLine();
foreach (var tag in stage.tags)
{
builder.Append("G|").Append(tag).AppendLine();
}
foreach (var tile in stage.occupiedTiles)
{
builder.Append("T|").Append(tile.x).Append('|').Append(tile.y).AppendLine();
}
}
foreach (var endpoint in graph.endpoints)
{
builder.Append("E|").Append(endpoint.id).Append('|').Append(endpoint.stageId).Append('|')
.Append(endpoint.worldTile.x).Append('|').Append(endpoint.worldTile.y).Append('|')
.Append(endpoint.kind).Append('|').Append(endpoint.required).Append('|')
.Append(endpoint.socketFace).AppendLine();
}
foreach (var connection in graph.connections)
{
builder.Append("C|").Append(connection.id).Append('|').Append(connection.endpointAId).Append('|')
.Append(connection.endpointBId).Append('|').Append(connection.directionality).AppendLine();
}
using var sha = SHA256.Create();
var bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(builder.ToString()));
var result = new StringBuilder(bytes.Length * 2);
foreach (var value in bytes)
{
result.Append(value.ToString("x2"));
}
return result.ToString();
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 5b08cddf1c414e7b8d1f6ee4d7e67fb7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,19 +0,0 @@
using UnityEngine;
namespace Editor.GON.WorldEditor
{
public static class WorldEditorColors
{
public static readonly Color Background = new(0.08f, 0.09f, 0.1f, 1f);
public static readonly Color Grid = new(1f, 1f, 1f, 0.08f);
public static readonly Color GridAxis = new(1f, 1f, 1f, 0.18f);
public static readonly Color Selected = new(1f, 0.86f, 0.32f, 1f);
public static readonly Color EndpointSelected = new(1f, 0.1f, 0.95f, 1f);
public static readonly Color Socket = new(0.45f, 0.45f, 0.45f, 1f);
public static readonly Color SocketPaired = new(1f, 0.86f, 0.32f, 1f);
public static readonly Color SocketConnected = new(0.35f, 0.95f, 0.45f, 1f);
public static readonly Color Anchor = new(0.35f, 0.9f, 0.75f, 1f);
public static readonly Color Lift = new(0.95f, 0.45f, 0.35f, 1f);
public static readonly Color LiftGhost = new(0.95f, 0.45f, 0.35f, 0.35f);
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: c934ed58cc61487aaf68600c93e0d8b3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,28 +0,0 @@
namespace Editor.GON.WorldEditor
{
using System;
using Intrepid.Gameplay.Journey;
public enum WorldEditorTool
{
Select = 0,
PaintStage = 1,
EraseStage = 2,
Socket = 3,
Anchor = 4,
Lift = 5
}
public sealed class WorldEditorState
{
public int activeLayer;
public string selectedStageId;
public string selectedEndpointId;
public string selectedConnectionId;
public WorldEditorTool activeTool = WorldEditorTool.PaintStage;
public JWorldTile? hoverTile;
public string pendingAnchorEndpointId;
public WorldStageKind? pendingStagePlacementKind;
public WorldEditorTool toolBeforeStagePlacement;
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 6782fb3fdb8f49d5939b0c92f86adad9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,942 +0,0 @@
using System;
using System.Collections.Generic;
using Intrepid.Gameplay.Journey;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
using Random = UnityEngine.Random;
namespace Editor.GON.WorldEditor
{
public sealed class WorldEditorWindow : EditorWindow
{
private WorldLayoutSO layout;
private JGraphSO graph;
private WorldEditorState state;
private VisualElement topBar;
private VisualElement layerBar;
private VisualElement toolBar;
private WorldMapCanvas canvas;
private WorldInspectorPanel inspector;
private Label infoLabel;
[MenuItem("Tools/Gardens Of Nil/World Editor")]
public static void Open()
{
var window = GetWindow<WorldEditorWindow>();
window.titleContent = new GUIContent("Gardens World Editor");
window.minSize = new Vector2(900, 600);
window.Show();
}
public void CreateGUI()
{
state ??= new WorldEditorState();
rootVisualElement.Clear();
rootVisualElement.style.flexDirection = FlexDirection.Column;
topBar = new VisualElement
{
style =
{
flexDirection = FlexDirection.Row,
alignItems = Align.Center,
paddingLeft = 8,
paddingRight = 8,
paddingTop = 6,
paddingBottom = 6,
borderBottomWidth = 1,
borderBottomColor = new Color(1f, 1f, 1f, 0.12f)
}
};
rootVisualElement.Add(topBar);
var main = new VisualElement
{
style =
{
flexGrow = 1f,
flexDirection = FlexDirection.Row
}
};
rootVisualElement.Add(main);
layerBar = new VisualElement
{
style =
{
width = 74,
flexShrink = 0f,
paddingTop = 6,
paddingLeft = 6,
paddingRight = 6,
borderRightWidth = 1,
borderRightColor = new Color(1f, 1f, 1f, 0.12f)
}
};
main.Add(layerBar);
canvas = new WorldMapCanvas(this, state);
canvas.SetLayout(layout);
main.Add(canvas);
inspector = new WorldInspectorPanel(this, state);
inspector.SetLayout(layout);
main.Add(inspector);
infoLabel = new Label
{
style =
{
paddingLeft = 8,
paddingTop = 4,
paddingBottom = 4
}
};
rootVisualElement.Add(infoLabel);
toolBar = new VisualElement
{
style =
{
flexDirection = FlexDirection.Row,
alignItems = Align.Center,
paddingLeft = 8,
paddingRight = 8,
paddingTop = 6,
paddingBottom = 6,
borderTopWidth = 1,
borderTopColor = new Color(1f, 1f, 1f, 0.12f)
}
};
rootVisualElement.Add(toolBar);
RebuildTopBar();
RebuildLayerBar();
RebuildToolBar();
RebuildInfo();
}
public void MutateLayout(string undoName, Action mutation)
{
if (layout is null || mutation is null)
{
return;
}
var previousStageId = state.selectedStageId;
var previousEndpointId = state.selectedEndpointId;
var previousConnectionId = state.selectedConnectionId;
Undo.RecordObject(layout, undoName);
mutation();
layout.EnsureDefaults();
layout.PruneEmptyStagesAndDanglingConnections();
ClearInvalidSelection();
EditorUtility.SetDirty(layout);
if (previousStageId != state.selectedStageId ||
previousEndpointId != state.selectedEndpointId ||
previousConnectionId != state.selectedConnectionId)
{
SyncActiveLayerToSelection(true);
}
canvas?.RequestRepaint();
RebuildTopBar();
RebuildLayerBar();
RebuildToolBar();
inspector?.Rebuild();
RebuildInfo();
Repaint();
}
public WorldLayoutSO Layout => layout;
public void SetHoverTile(JWorldTile? tile)
{
state.hoverTile = tile;
RebuildInfo();
}
public void RequestRebuildInfo()
{
RebuildInfo();
}
public void RequestSelectionRebuild()
{
SyncActiveLayerToSelection(true);
canvas?.RequestRepaint();
RebuildLayerBar();
inspector?.Rebuild();
RebuildInfo();
Repaint();
}
public void BeginStagePlacement(WorldStageKind stageKind)
{
if (state.pendingStagePlacementKind is null)
{
state.toolBeforeStagePlacement = state.activeTool;
}
state.pendingStagePlacementKind = stageKind;
state.pendingAnchorEndpointId = null;
canvas?.RequestRepaint();
RebuildTopBar();
RebuildToolBar();
RebuildInfo();
Repaint();
}
public void CancelStagePlacement()
{
if (state.pendingStagePlacementKind is null)
{
return;
}
state.pendingStagePlacementKind = null;
state.activeTool = state.toolBeforeStagePlacement;
canvas?.RequestRepaint();
RebuildTopBar();
RebuildToolBar();
RebuildInfo();
Repaint();
}
public void SelectStage(string stageId)
{
state.selectedStageId = stageId;
state.selectedEndpointId = null;
state.selectedConnectionId = null;
RequestSelectionRebuild();
}
public void SelectEndpoint(string endpointId)
{
var endpoint = layout?.FindEndpoint(endpointId);
if (endpoint is null)
{
return;
}
state.selectedStageId = endpoint.stageId;
state.selectedEndpointId = endpoint.id;
state.selectedConnectionId = null;
RequestSelectionRebuild();
}
public void SelectConnection(string connectionId)
{
var connection = FindConnection(connectionId);
if (connection is null)
{
return;
}
var endpointA = layout.FindEndpoint(connection.endpointAId);
var endpointB = layout.FindEndpoint(connection.endpointBId);
var endpoint = PickConnectionEndpointForCurrentLayer(endpointA, endpointB);
state.selectedStageId = endpoint?.stageId;
state.selectedEndpointId = null;
state.selectedConnectionId = connection.id;
RequestSelectionRebuild();
}
private void RebuildTopBar()
{
if (topBar is null)
{
return;
}
topBar.Clear();
var layoutField = new ObjectField("World Layout")
{
objectType = typeof(WorldLayoutSO),
value = layout,
style =
{
minWidth = 240
}
};
layoutField.RegisterValueChangedCallback(evt => SetLayout(evt.newValue as WorldLayoutSO));
topBar.Add(layoutField);
topBar.Add(new Button(CreateNewLayoutAsset)
{
text = "New",
style = { marginLeft = 6 }
});
if (layout is null)
{
return;
}
layout.EnsureDefaults();
var graphField = new ObjectField("J Graph")
{
objectType = typeof(JGraphSO),
value = graph,
style =
{
marginLeft = 8,
minWidth = 200
}
};
graphField.RegisterValueChangedCallback(evt =>
{
graph = evt.newValue as JGraphSO;
RebuildTopBar();
});
topBar.Add(graphField);
topBar.Add(new Button(CreateNewGraphAsset)
{
text = "+ JGraphSO",
style = { marginLeft = 4 }
});
topBar.Add(new Button(ValidateLayout)
{
text = "Validate",
style = { marginLeft = 8 }
});
var commit = new Button(CommitJourney)
{
text = "Commit Journey",
style = { marginLeft = 4 }
};
commit.SetEnabled(graph is not null);
topBar.Add(commit);
topBar.Add(new Button(SaveLayout)
{
text = "Save",
style = { marginLeft = 8 }
});
}
private void RebuildLayerBar()
{
if (layerBar is null)
{
return;
}
layerBar.Clear();
if (layout is null)
{
layerBar.Add(new Label("Layers"));
return;
}
layout.EnsureDefaults();
state.activeLayer = layout.ClampLayer(state.activeLayer);
var title = new Label("Layers")
{
style =
{
unityFontStyleAndWeight = FontStyle.Bold,
marginBottom = 4
}
};
layerBar.Add(title);
var topControls = new VisualElement
{
style =
{
flexDirection = FlexDirection.Row,
marginBottom = 6
}
};
var addTopLayer = new Button(delegate
{
MutateLayout("Add upper world layer", delegate
{
layout.maxWorldLayer += 1;
state.activeLayer = layout.maxWorldLayer;
});
})
{
text = "+",
style =
{
flexGrow = 1f,
marginRight = 2,
height = 24
}
};
topControls.Add(addTopLayer);
var removeTopLayer = new Button(delegate
{
MutateLayout("Remove upper world layer", delegate
{
if (layout.maxWorldLayer > layout.minWorldLayer && !LayerHasStages(layout.maxWorldLayer))
{
layout.maxWorldLayer -= 1;
state.activeLayer = layout.ClampLayer(state.activeLayer);
}
});
})
{
text = "-",
style =
{
flexGrow = 1f,
marginLeft = 2,
height = 24
}
};
removeTopLayer.SetEnabled(layout.maxWorldLayer > layout.minWorldLayer && !LayerHasStages(layout.maxWorldLayer));
topControls.Add(removeTopLayer);
layerBar.Add(topControls);
for (var layer = layout.maxWorldLayer; layer >= layout.minWorldLayer; layer--)
{
var capturedLayer = layer;
var button = new Button(delegate
{
state.activeLayer = capturedLayer;
canvas?.RequestRepaint();
RebuildLayerBar();
inspector?.Rebuild();
RebuildInfo();
})
{
text = FormatLayer(capturedLayer),
style =
{
marginBottom = 3,
height = 26
}
};
if (capturedLayer == state.activeLayer)
{
button.style.backgroundColor = new Color(1f, 0.86f, 0.32f, 0.45f);
button.style.color = Color.white;
button.style.unityFontStyleAndWeight = FontStyle.Bold;
}
layerBar.Add(button);
}
var bottomControls = new VisualElement
{
style =
{
flexDirection = FlexDirection.Row,
marginTop = 6
}
};
var addBottomLayer = new Button(delegate
{
MutateLayout("Add lower world layer", delegate
{
layout.minWorldLayer -= 1;
state.activeLayer = layout.minWorldLayer;
});
})
{
text = "+",
style =
{
flexGrow = 1f,
marginRight = 2,
height = 24
}
};
bottomControls.Add(addBottomLayer);
var removeBottomLayer = new Button(delegate
{
MutateLayout("Remove lower world layer", delegate
{
if (layout.minWorldLayer < layout.maxWorldLayer && !LayerHasStages(layout.minWorldLayer))
{
layout.minWorldLayer += 1;
state.activeLayer = layout.ClampLayer(state.activeLayer);
}
});
})
{
text = "-",
style =
{
flexGrow = 1f,
marginLeft = 2,
height = 24
}
};
removeBottomLayer.SetEnabled(layout.minWorldLayer < layout.maxWorldLayer && !LayerHasStages(layout.minWorldLayer));
bottomControls.Add(removeBottomLayer);
layerBar.Add(bottomControls);
}
private bool LayerHasStages(int layer)
{
if (layout is null)
{
return false;
}
foreach (var stage in layout.stages)
{
if (stage != null && stage.worldLayer == layer)
{
return true;
}
}
return false;
}
private void RebuildToolBar()
{
if (toolBar is null)
{
return;
}
toolBar.Clear();
toolBar.Add(new Label("Units")
{
style =
{
unityFontStyleAndWeight = FontStyle.Bold,
marginRight = 8
}
});
AddUnitButton(WorldStageKind.Stage, "+ Stage", "Placing Stage");
AddUnitButton(WorldStageKind.SavePoint, "+ Save Point", "Placing Save");
var separator = new VisualElement
{
style =
{
width = 1,
height = 24,
marginLeft = 8,
marginRight = 8,
backgroundColor = new Color(1f, 1f, 1f, 0.18f)
}
};
toolBar.Add(separator);
toolBar.Add(new Label("Tools")
{
style =
{
unityFontStyleAndWeight = FontStyle.Bold,
marginRight = 8
}
});
AddToolButton(WorldEditorTool.Select, "Select");
AddToolButton(WorldEditorTool.PaintStage, "Paint");
AddToolButton(WorldEditorTool.EraseStage, "Erase");
AddToolButton(WorldEditorTool.Socket, "Socket");
AddToolButton(WorldEditorTool.Anchor, "Anchor");
AddToolButton(WorldEditorTool.Lift, "Lift");
}
private void AddUnitButton(WorldStageKind stageKind, string label, string placingLabel)
{
var button = new Button(delegate { BeginStagePlacement(stageKind); })
{
text = state.pendingStagePlacementKind == stageKind ? placingLabel : label,
style =
{
marginRight = 4,
minWidth = stageKind == WorldStageKind.SavePoint ? 110 : 82,
height = 28
}
};
if (state.pendingStagePlacementKind == stageKind)
{
button.style.backgroundColor = new Color(1f, 0.1f, 0.95f, 0.35f);
button.style.color = Color.white;
button.style.unityFontStyleAndWeight = FontStyle.Bold;
}
toolBar.Add(button);
}
private void AddToolButton(WorldEditorTool tool, string label)
{
var button = new Button(delegate
{
state.activeTool = tool;
canvas?.RequestRepaint();
RebuildToolBar();
inspector?.Rebuild();
RebuildInfo();
})
{
text = label,
style =
{
marginRight = 4,
minWidth = 76,
height = 28
}
};
if (state.activeTool == tool)
{
button.style.backgroundColor = new Color(1f, 0.86f, 0.32f, 0.45f);
button.style.color = Color.white;
button.style.unityFontStyleAndWeight = FontStyle.Bold;
}
toolBar.Add(button);
}
private static string FormatLayer(int layer)
{
if (layer > 0)
{
return "+" + layer;
}
return layer.ToString();
}
private void RebuildInfo()
{
if (infoLabel is null)
{
return;
}
if (layout is null)
{
infoLabel.text = "Select or create a WorldLayoutSO.";
return;
}
var selected = layout.FindStage(state.selectedStageId);
var selectedEndpoint = layout.FindEndpoint(state.selectedEndpointId);
var selectedConnection = FindConnection(state.selectedConnectionId);
var hoverText = state.hoverTile.HasValue
? " | Tile: (" + state.hoverTile.Value.x + ", " + state.hoverTile.Value.y + ")"
: " | Tile: -";
var anchorText = string.IsNullOrEmpty(state.pendingAnchorEndpointId)
? string.Empty
: " | Anchor link armed";
var placementText = state.pendingStagePlacementKind.HasValue
? " | Placing " + state.pendingStagePlacementKind.Value + " (click to deploy, Esc to cancel)"
: string.Empty;
var selectionText = "No selection.";
if (selectedConnection is not null)
{
selectionText = "Selected connection: " + selectedConnection.id;
}
else if (selectedEndpoint is not null)
{
selectionText = "Selected " + selectedEndpoint.kind + ": " + selectedEndpoint.id;
}
else if (selected is not null)
{
selectionText = "Selected: " + selected.displayName + " (" + selected.id + ")";
}
infoLabel.text = selectionText + hoverText + anchorText + placementText;
}
private void SetLayout(WorldLayoutSO newLayout)
{
layout = newLayout;
layout?.EnsureDefaults();
if (layout is not null)
{
state.activeLayer = layout.ClampLayer(state.activeLayer);
}
if (layout?.FindStage(state.selectedStageId) is null)
{
state.selectedStageId = null;
}
if (layout?.FindEndpoint(state.selectedEndpointId) is null)
{
state.selectedEndpointId = null;
}
if (FindConnection(state.selectedConnectionId) is null)
{
state.selectedConnectionId = null;
}
canvas?.SetLayout(layout);
inspector?.SetLayout(layout);
RebuildTopBar();
RebuildLayerBar();
inspector?.Rebuild();
RebuildInfo();
}
private void ClearInvalidSelection()
{
if (layout is null)
{
state.selectedStageId = null;
state.selectedEndpointId = null;
state.selectedConnectionId = null;
return;
}
if (layout.FindStage(state.selectedStageId) is null)
{
state.selectedStageId = null;
}
if (layout.FindEndpoint(state.selectedEndpointId) is null)
{
state.selectedEndpointId = null;
}
if (FindConnection(state.selectedConnectionId) is null)
{
state.selectedConnectionId = null;
}
}
private void SyncActiveLayerToSelection(bool frameWhenLayerChanges)
{
if (layout is null)
{
return;
}
layout.EnsureDefaults();
var previousLayer = state.activeLayer;
var selectedEndpoint = layout.FindEndpoint(state.selectedEndpointId);
if (selectedEndpoint is not null)
{
var endpointStage = layout.FindStage(selectedEndpoint.stageId);
if (endpointStage is not null)
{
state.activeLayer = layout.ClampLayer(endpointStage.worldLayer);
FrameSelectionIfLayerChanged(previousLayer, frameWhenLayerChanges);
return;
}
}
var selectedConnection = FindConnection(state.selectedConnectionId);
if (selectedConnection is not null)
{
var endpointA = layout.FindEndpoint(selectedConnection.endpointAId);
var endpointB = layout.FindEndpoint(selectedConnection.endpointBId);
var endpoint = PickConnectionEndpointForSelectedStage(endpointA, endpointB) ??
PickConnectionEndpointForCurrentLayer(endpointA, endpointB);
var endpointStage = endpoint is null ? null : layout.FindStage(endpoint.stageId);
if (endpointStage is not null)
{
state.activeLayer = layout.ClampLayer(endpointStage.worldLayer);
FrameSelectionIfLayerChanged(previousLayer, frameWhenLayerChanges);
return;
}
}
var selectedStage = layout.FindStage(state.selectedStageId);
if (selectedStage is not null)
{
state.activeLayer = layout.ClampLayer(selectedStage.worldLayer);
FrameSelectionIfLayerChanged(previousLayer, frameWhenLayerChanges);
}
}
private void FrameSelectionIfLayerChanged(int previousLayer, bool frameWhenLayerChanges)
{
if (frameWhenLayerChanges && previousLayer != state.activeLayer)
{
canvas?.FrameSelection();
}
}
private WorldEndpointAuthoring PickConnectionEndpointForSelectedStage(
WorldEndpointAuthoring endpointA,
WorldEndpointAuthoring endpointB)
{
if (endpointA is not null && endpointA.stageId == state.selectedStageId)
{
return endpointA;
}
if (endpointB is not null && endpointB.stageId == state.selectedStageId)
{
return endpointB;
}
return null;
}
private WorldEndpointAuthoring PickConnectionEndpointForCurrentLayer(
WorldEndpointAuthoring endpointA,
WorldEndpointAuthoring endpointB)
{
var stageA = endpointA is null ? null : layout.FindStage(endpointA.stageId);
if (stageA is not null && stageA.worldLayer == state.activeLayer)
{
return endpointA;
}
var stageB = endpointB is null ? null : layout.FindStage(endpointB.stageId);
if (stageB is not null && stageB.worldLayer == state.activeLayer)
{
return endpointB;
}
return endpointA ?? endpointB;
}
private void CreateNewLayoutAsset()
{
var path = EditorUtility.SaveFilePanelInProject(
"Create WorldLayoutSO",
"WorldLayout",
"asset",
"Choose where to create the new WorldLayoutSO asset.");
if (string.IsNullOrEmpty(path))
{
return;
}
var asset = CreateInstance<WorldLayoutSO>();
asset.EnsureDefaults();
AssetDatabase.CreateAsset(asset, path);
AssetDatabase.SaveAssets();
SetLayout(asset);
}
private void CreateNewGraphAsset()
{
var path = EditorUtility.SaveFilePanelInProject(
"Create JGraphSO",
"JGraph",
"asset",
"Choose where to create the published Journey graph asset.");
if (string.IsNullOrEmpty(path))
{
return;
}
var asset = CreateInstance<JGraphSO>();
AssetDatabase.CreateAsset(asset, path);
AssetDatabase.SaveAssets();
graph = asset;
RebuildTopBar();
}
private void CreateStage()
{
BeginStagePlacement(WorldStageKind.Stage);
}
private void CreateSavePoint()
{
BeginStagePlacement(WorldStageKind.SavePoint);
}
private WorldConnectionAuthoring FindConnection(string connectionId)
{
if (layout is null || string.IsNullOrEmpty(connectionId))
{
return null;
}
layout.EnsureDefaults();
foreach (var connection in layout.connections)
{
if (connection != null && connection.id == connectionId)
{
return connection;
}
}
return null;
}
private void SaveLayout()
{
if (layout is null)
{
return;
}
EditorUtility.SetDirty(layout);
AssetDatabase.SaveAssets();
Debug.Log("Saved WorldLayoutSO: " + AssetDatabase.GetAssetPath(layout));
}
private void ValidateLayout()
{
MutateLayout("Repair world layout before validation", delegate
{
layout.PruneEmptyStagesAndDanglingConnections();
});
ShowValidationMessages(WorldLayoutValidator.Validate(layout));
}
private void CommitJourney()
{
var messages = JourneyCommitter.Commit(layout, graph);
ShowValidationMessages(messages);
if (!WorldLayoutValidator.HasErrors(messages))
{
Debug.Log("Committed Journey graph: " + AssetDatabase.GetAssetPath(graph));
}
}
private void ShowValidationMessages(List<WorldValidationMessage> messages)
{
if (messages is null || messages.Count == 0)
{
Debug.Log("World Editor validation passed.");
return;
}
foreach (var message in messages)
{
if (message is null)
{
continue;
}
LogValidationMessage(message);
}
}
private static void LogValidationMessage(WorldValidationMessage message)
{
var text = "World Editor " + message.severity + ": " + message.message +
(string.IsNullOrEmpty(message.objectId) ? string.Empty : " [" + message.objectId + "]");
if (message.severity == WorldValidationSeverity.Error)
{
Debug.LogError(text);
return;
}
Debug.LogWarning(text);
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: a24a25a37da44f078a55114d872d2f66
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,308 +0,0 @@
using System.Collections.Generic;
using Editor.GON.StageEditor;
using Intrepid.Gameplay.Journey;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
namespace Editor.GON.WorldEditor
{
public sealed class WorldInspectorPanel : VisualElement
{
private readonly WorldEditorWindow owner;
private readonly WorldEditorState state;
private WorldLayoutSO worldLayout;
public WorldInspectorPanel(WorldEditorWindow owner, WorldEditorState state)
{
this.owner = owner;
this.state = state;
style.width = 300;
style.minWidth = 260;
style.maxWidth = 380;
style.paddingLeft = 8;
style.paddingRight = 8;
style.paddingTop = 8;
style.paddingBottom = 8;
style.borderLeftWidth = 1;
style.borderLeftColor = new Color(1f, 1f, 1f, 0.12f);
style.flexShrink = 0;
}
public void SetLayout(WorldLayoutSO newLayout)
{
worldLayout = newLayout;
Rebuild();
}
public void Rebuild()
{
Clear();
Add(new Label("Inspector")
{
style =
{
unityFontStyleAndWeight = FontStyle.Bold,
marginBottom = 8
}
});
if (worldLayout is null)
{
Add(new HelpBox("Select or create a WorldLayoutSO asset.", HelpBoxMessageType.Info));
return;
}
worldLayout.EnsureDefaults();
var selectedConnection = FindConnection(state.selectedConnectionId);
if (selectedConnection is not null)
{
BuildConnectionInspector(selectedConnection);
return;
}
var selectedEndpoint = worldLayout.FindEndpoint(state.selectedEndpointId);
if (selectedEndpoint is not null)
{
BuildEndpointInspector(selectedEndpoint);
return;
}
var selectedStage = worldLayout.FindStage(state.selectedStageId);
if (selectedStage is not null)
{
BuildStageInspector(selectedStage);
return;
}
Add(new HelpBox("No world item selected.", HelpBoxMessageType.Info));
}
private void BuildStageInspector(WorldStageAuthoring stage)
{
AddSection("Stage");
AddReadOnlyLine("Id", stage.id);
AddReadOnlyLine("Kind", stage.stageKind.ToString());
AddReadOnlyLine("Layer", FormatLayer(stage.worldLayer));
AddReadOnlyLine("Tiles", stage.occupiedTiles.Count.ToString());
AddReadOnlyLine("Endpoints", CountStageEndpoints(stage.id).ToString());
var kind = new EnumField("Kind", stage.stageKind);
kind.RegisterValueChangedCallback(evt => owner.MutateLayout("Change world stage kind", delegate
{
stage.stageKind = (WorldStageKind)evt.newValue;
worldLayout.EnforceSavePointRules(stage);
}));
Add(kind);
var name = new TextField("Name")
{
value = stage.displayName
};
name.RegisterValueChangedCallback(evt => owner.MutateLayout("Rename world stage", delegate
{
stage.displayName = string.IsNullOrWhiteSpace(evt.newValue) ? "Stage" : evt.newValue;
}));
Add(name);
var color = new ColorField("Color")
{
value = stage.color
};
color.RegisterValueChangedCallback(evt => owner.MutateLayout("Change world stage color", delegate
{
stage.color = evt.newValue;
}));
Add(color);
var definition = new ObjectField("Stage Definition")
{
objectType = typeof(StageDefinition),
value = stage.stageDefinition
};
definition.RegisterValueChangedCallback(evt => owner.MutateLayout("Assign stage definition", delegate
{
stage.stageDefinition = evt.newValue as StageDefinition;
}));
Add(definition);
}
private void BuildEndpointInspector(WorldEndpointAuthoring endpoint)
{
var stage = worldLayout.FindStage(endpoint.stageId);
AddSection(endpoint.kind.ToString());
AddReadOnlyLine("Id", endpoint.id);
AddReadOnlyLine("Stage", stage is null ? endpoint.stageId : stage.displayName);
AddReadOnlyLine("Layer", stage is null ? "-" : FormatLayer(stage.worldLayer));
AddReadOnlyLine("Tile", "(" + endpoint.worldTile.x + ", " + endpoint.worldTile.y + ")");
if (endpoint.kind == JEndpointKind.Socket)
{
AddReadOnlyLine("Face", endpoint.socketFace.ToString());
}
var required = new Toggle("Required")
{
value = endpoint.required
};
required.RegisterValueChangedCallback(evt => owner.MutateLayout("Toggle endpoint required", delegate
{
endpoint.required = evt.newValue;
}));
Add(required);
var connections = FindConnectionsForEndpoint(endpoint.id);
AddReadOnlyLine("Connections", connections.Count.ToString());
foreach (var connection in connections)
{
AddConnectionButton(connection);
}
}
private void BuildConnectionInspector(WorldConnectionAuthoring connection)
{
AddSection("JConnection");
AddReadOnlyLine("Id", connection.id);
var endpointA = worldLayout.FindEndpoint(connection.endpointAId);
var endpointB = worldLayout.FindEndpoint(connection.endpointBId);
AddEndpointButton("Endpoint A", endpointA, connection.endpointAId);
AddEndpointButton("Endpoint B", endpointB, connection.endpointBId);
var directionality = new EnumField("Direction", connection.directionality);
directionality.RegisterValueChangedCallback(evt => owner.MutateLayout("Change connection direction", delegate
{
connection.directionality = (JConnectionDirectionality)evt.newValue;
}));
Add(directionality);
}
private void AddConnectionButton(WorldConnectionAuthoring connection)
{
var button = new Button(delegate
{
owner.SelectConnection(connection.id);
})
{
text = "Connection: " + connection.id,
style =
{
marginTop = 4,
unityTextAlign = TextAnchor.MiddleLeft
}
};
Add(button);
}
private void AddEndpointButton(string label, WorldEndpointAuthoring endpoint, string fallbackId)
{
var text = endpoint is null
? fallbackId + " (missing)"
: endpoint.kind + " " + endpoint.id;
var button = new Button(delegate
{
if (endpoint is null)
{
return;
}
owner.SelectEndpoint(endpoint.id);
})
{
text = label + ": " + text,
style =
{
marginTop = 4,
unityTextAlign = TextAnchor.MiddleLeft
}
};
button.SetEnabled(endpoint is not null);
Add(button);
}
private List<WorldConnectionAuthoring> FindConnectionsForEndpoint(string endpointId)
{
var result = new List<WorldConnectionAuthoring>();
foreach (var connection in worldLayout.connections)
{
if (connection == null)
{
continue;
}
if (connection.endpointAId == endpointId || connection.endpointBId == endpointId)
{
result.Add(connection);
}
}
return result;
}
private WorldConnectionAuthoring FindConnection(string connectionId)
{
if (string.IsNullOrEmpty(connectionId))
{
return null;
}
foreach (var connection in worldLayout.connections)
{
if (connection != null && connection.id == connectionId)
{
return connection;
}
}
return null;
}
private int CountStageEndpoints(string stageId)
{
var count = 0;
foreach (var endpoint in worldLayout.endpoints)
{
if (endpoint != null && endpoint.stageId == stageId)
{
count++;
}
}
return count;
}
private void AddSection(string text)
{
Add(new Label(text)
{
style =
{
unityFontStyleAndWeight = FontStyle.Bold,
marginTop = 4,
marginBottom = 6
}
});
}
private void AddReadOnlyLine(string label, string value)
{
Add(new Label(label + ": " + value)
{
style =
{
marginBottom = 3,
opacity = 0.85f
}
});
}
private static string FormatLayer(int layer)
{
return layer > 0 ? "+" + layer : layer.ToString();
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 2cf93c2a33314ae690dec73cb9b210e9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,418 +0,0 @@
using System;
using System.Collections.Generic;
using Editor.GON.StageEditor;
using Intrepid.Gameplay.Journey;
using UnityEngine;
namespace Editor.GON.WorldEditor
{
public enum WorldStageKind
{
Stage = 0,
SavePoint = 1
}
[CreateAssetMenu(menuName = "GON/Editor/World Layout", fileName = "WorldLayout")]
public sealed class WorldLayoutSO : ScriptableObject
{
public string id;
public float worldTileSize = 24f;
public float worldLayerHeight = 24f;
public const string SavePointTag = "SavePoint";
public int minWorldLayer = -2;
public int maxWorldLayer = 2;
public List<WorldStageAuthoring> stages = new();
public List<WorldEndpointAuthoring> endpoints = new();
public List<WorldConnectionAuthoring> connections = new();
public void EnsureDefaults()
{
if (string.IsNullOrWhiteSpace(id))
{
id = Guid.NewGuid().ToString("N");
}
if (worldTileSize <= 0f)
{
worldTileSize = 24f;
}
if (worldLayerHeight <= 0f)
{
worldLayerHeight = 24f;
}
if (minWorldLayer > maxWorldLayer)
{
(minWorldLayer, maxWorldLayer) = (maxWorldLayer, minWorldLayer);
}
stages ??= new List<WorldStageAuthoring>();
endpoints ??= new List<WorldEndpointAuthoring>();
connections ??= new List<WorldConnectionAuthoring>();
foreach (var stage in stages)
{
stage?.EnsureDefaults();
}
}
public int ClampLayer(int layer)
{
EnsureDefaults();
return Mathf.Clamp(layer, minWorldLayer, maxWorldLayer);
}
public string CreateId(string prefix)
{
return prefix + "_" + Guid.NewGuid().ToString("N");
}
public WorldStageAuthoring FindStage(string stageId)
{
EnsureDefaults();
foreach (var stage in stages)
{
if (stage != null && stage.id == stageId)
{
return stage;
}
}
return null;
}
public WorldStageAuthoring FindStageAt(int worldLayer, JWorldTile tile)
{
EnsureDefaults();
foreach (var stage in stages)
{
if (stage != null && stage.worldLayer == worldLayer && stage.ContainsTile(tile))
{
return stage;
}
}
return null;
}
public WorldEndpointAuthoring FindEndpoint(string endpointId)
{
EnsureDefaults();
foreach (var endpoint in endpoints)
{
if (endpoint != null && endpoint.id == endpointId)
{
return endpoint;
}
}
return null;
}
public bool IsTileOccupiedByOther(string stageId, int worldLayer, JWorldTile tile)
{
var occupyingStage = FindStageAt(worldLayer, tile);
return occupyingStage != null && occupyingStage.id != stageId;
}
public WorldEndpointAuthoring FindEndpointAt(string stageId, JWorldTile tile, JEndpointKind kind)
{
EnsureDefaults();
foreach (var endpoint in endpoints)
{
if (endpoint != null && endpoint.stageId == stageId && endpoint.kind == kind &&
endpoint.worldTile.x == tile.x && endpoint.worldTile.y == tile.y)
{
return endpoint;
}
}
return null;
}
public WorldEndpointAuthoring FindSocketAt(string stageId, JWorldTile tile, JSocketFace face)
{
EnsureDefaults();
foreach (var endpoint in endpoints)
{
if (endpoint != null &&
endpoint.stageId == stageId &&
endpoint.kind == JEndpointKind.Socket &&
endpoint.socketFace == face &&
endpoint.worldTile.x == tile.x &&
endpoint.worldTile.y == tile.y)
{
return endpoint;
}
}
return null;
}
public WorldConnectionAuthoring FindConnectionBetween(string endpointAId, string endpointBId)
{
EnsureDefaults();
foreach (var connection in connections)
{
if (connection == null)
{
continue;
}
if ((connection.endpointAId == endpointAId && connection.endpointBId == endpointBId) ||
(connection.endpointAId == endpointBId && connection.endpointBId == endpointAId))
{
return connection;
}
}
return null;
}
public void RemoveEndpointAndConnections(string endpointId)
{
if (string.IsNullOrEmpty(endpointId))
{
return;
}
EnsureDefaults();
for (var i = endpoints.Count - 1; i >= 0; i--)
{
if (endpoints[i] != null && endpoints[i].id == endpointId)
{
endpoints.RemoveAt(i);
}
}
for (var i = connections.Count - 1; i >= 0; i--)
{
var connection = connections[i];
if (connection == null ||
connection.endpointAId == endpointId ||
connection.endpointBId == endpointId)
{
connections.RemoveAt(i);
}
}
}
public void RemoveStageAndOwnedData(string stageId)
{
if (string.IsNullOrEmpty(stageId))
{
return;
}
EnsureDefaults();
var endpointIds = new List<string>();
foreach (var endpoint in endpoints)
{
if (endpoint != null && endpoint.stageId == stageId)
{
endpointIds.Add(endpoint.id);
}
}
foreach (var endpointId in endpointIds)
{
RemoveEndpointAndConnections(endpointId);
}
for (var i = stages.Count - 1; i >= 0; i--)
{
if (stages[i] != null && stages[i].id == stageId)
{
stages.RemoveAt(i);
}
}
}
public void RemoveEndpointsAtTile(string stageId, JWorldTile tile)
{
if (string.IsNullOrEmpty(stageId))
{
return;
}
EnsureDefaults();
var endpointIds = new List<string>();
foreach (var endpoint in endpoints)
{
if (endpoint != null &&
endpoint.stageId == stageId &&
endpoint.worldTile.x == tile.x &&
endpoint.worldTile.y == tile.y)
{
endpointIds.Add(endpoint.id);
}
}
foreach (var endpointId in endpointIds)
{
RemoveEndpointAndConnections(endpointId);
}
}
public bool PruneEmptyStagesAndDanglingConnections()
{
EnsureDefaults();
var changed = false;
for (var i = stages.Count - 1; i >= 0; i--)
{
var stage = stages[i];
stage?.EnsureDefaults();
if (stage == null || stage.occupiedTiles.Count > 0)
{
continue;
}
RemoveStageAndOwnedData(stage.id);
changed = true;
}
for (var i = endpoints.Count - 1; i >= 0; i--)
{
var endpoint = endpoints[i];
var stage = endpoint == null ? null : FindStage(endpoint.stageId);
if (endpoint == null || stage == null || !stage.ContainsTile(endpoint.worldTile))
{
if (endpoint != null)
{
RemoveEndpointAndConnections(endpoint.id);
}
else
{
endpoints.RemoveAt(i);
}
changed = true;
}
}
for (var i = connections.Count - 1; i >= 0; i--)
{
var connection = connections[i];
if (connection == null ||
FindEndpoint(connection.endpointAId) == null ||
FindEndpoint(connection.endpointBId) == null)
{
connections.RemoveAt(i);
changed = true;
}
}
return changed;
}
public void EnforceSavePointRules(WorldStageAuthoring stage)
{
if (stage is null || stage.stageKind != WorldStageKind.SavePoint)
{
return;
}
stage.EnsureDefaults();
if (stage.occupiedTiles.Count == 0)
{
stage.occupiedTiles.Add(new JWorldTile(0, 0));
}
var keptTile = stage.occupiedTiles[0];
stage.occupiedTiles.Clear();
stage.occupiedTiles.Add(keptTile);
for (var i = endpoints.Count - 1; i >= 0; i--)
{
var endpoint = endpoints[i];
if (endpoint == null || endpoint.stageId != stage.id)
{
continue;
}
var outsideSaveTile = endpoint.worldTile.x != keptTile.x || endpoint.worldTile.y != keptTile.y;
if (outsideSaveTile || endpoint.kind is JEndpointKind.Anchor or JEndpointKind.Lift)
{
RemoveEndpointAndConnections(endpoint.id);
}
}
}
}
[Serializable]
public sealed class WorldStageAuthoring
{
public string id;
public string displayName;
public WorldStageKind stageKind;
public int worldLayer;
public Color color = new(0.25f, 0.62f, 0.95f, 0.7f);
public StageDefinition stageDefinition;
public List<JWorldTile> occupiedTiles = new();
public void EnsureDefaults()
{
occupiedTiles ??= new List<JWorldTile>();
if (string.IsNullOrWhiteSpace(displayName))
{
displayName = "Stage";
}
}
public bool ContainsTile(JWorldTile tile)
{
EnsureDefaults();
foreach (var occupiedTile in occupiedTiles)
{
if (occupiedTile.x == tile.x && occupiedTile.y == tile.y)
{
return true;
}
}
return false;
}
public void AddTile(JWorldTile tile)
{
EnsureDefaults();
if (!ContainsTile(tile))
{
occupiedTiles.Add(tile);
}
}
public void RemoveTile(JWorldTile tile)
{
EnsureDefaults();
for (var i = occupiedTiles.Count - 1; i >= 0; i--)
{
if (occupiedTiles[i].x == tile.x && occupiedTiles[i].y == tile.y)
{
occupiedTiles.RemoveAt(i);
}
}
}
}
[Serializable]
public sealed class WorldEndpointAuthoring
{
public string id;
public string stageId;
public JWorldTile worldTile;
public JEndpointKind kind;
public bool required = true;
public JSocketFace socketFace;
}
[Serializable]
public sealed class WorldConnectionAuthoring
{
public string id;
public string endpointAId;
public string endpointBId;
public JConnectionDirectionality directionality = JConnectionDirectionality.Bidirectional;
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 8cbbff7dfcc24d2b9030f41f9f2ef718
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,306 +0,0 @@
using System.Collections.Generic;
using Intrepid.Gameplay.Journey;
using UnityEngine;
namespace Editor.GON.WorldEditor
{
public static class WorldLayoutValidator
{
public static List<WorldValidationMessage> Validate(WorldLayoutSO layout)
{
var messages = new List<WorldValidationMessage>();
if (layout is null)
{
messages.Add(new WorldValidationMessage(WorldValidationSeverity.Error, "No WorldLayoutSO selected."));
return messages;
}
layout.EnsureDefaults();
ValidateStages(layout, messages);
ValidateEndpoints(layout, messages);
ValidateConnections(layout, messages);
return messages;
}
public static bool HasErrors(List<WorldValidationMessage> messages)
{
if (messages is null)
{
return false;
}
foreach (var message in messages)
{
if (message != null && message.severity == WorldValidationSeverity.Error)
{
return true;
}
}
return false;
}
private static void ValidateStages(WorldLayoutSO layout, List<WorldValidationMessage> messages)
{
var occupied = new Dictionary<string, string>();
foreach (var stage in layout.stages)
{
if (stage is null)
{
continue;
}
stage.EnsureDefaults();
var endpointCount = 0;
foreach (var endpoint in layout.endpoints)
{
if (endpoint != null && endpoint.stageId == stage.id)
{
endpointCount++;
}
}
if (endpointCount == 0)
{
messages.Add(new WorldValidationMessage(WorldValidationSeverity.Warning,
"Stage has no Journey endpoints.", stage.id));
}
if (stage.stageKind == WorldStageKind.SavePoint && stage.occupiedTiles.Count != 1)
{
messages.Add(new WorldValidationMessage(WorldValidationSeverity.Error,
"Save Point stages must occupy exactly one WorldTile.", stage.id));
}
foreach (var tile in stage.occupiedTiles)
{
var key = TileKey(stage.worldLayer, tile);
if (occupied.TryGetValue(key, out var otherStageId) && otherStageId != stage.id)
{
messages.Add(new WorldValidationMessage(WorldValidationSeverity.Error,
"WorldTile is occupied by more than one Stage in the same WorldLayer.", stage.id));
}
else
{
occupied[key] = stage.id;
}
}
}
}
private static void ValidateEndpoints(WorldLayoutSO layout, List<WorldValidationMessage> messages)
{
var nonSocketByTile = new Dictionary<string, JEndpointKind>();
var socketByFace = new HashSet<string>();
foreach (var endpoint in layout.endpoints)
{
if (endpoint is null)
{
continue;
}
var stage = layout.FindStage(endpoint.stageId);
if (stage is null)
{
messages.Add(new WorldValidationMessage(WorldValidationSeverity.Error,
"JEndpoint references a missing Stage.", endpoint.id));
continue;
}
if (!stage.ContainsTile(endpoint.worldTile))
{
messages.Add(new WorldValidationMessage(WorldValidationSeverity.Error,
"JEndpoint is placed on a WorldTile not occupied by its Stage.", endpoint.id));
}
if (stage.stageKind == WorldStageKind.SavePoint &&
endpoint.kind is JEndpointKind.Anchor or JEndpointKind.Lift)
{
messages.Add(new WorldValidationMessage(WorldValidationSeverity.Error,
"Save Point stages cannot contain JEndpointAnchor or JEndpointLift.", endpoint.id));
}
if (endpoint.kind == JEndpointKind.Socket)
{
var socketKey = endpoint.stageId + ":" + endpoint.worldTile.x + ":" + endpoint.worldTile.y + ":" +
endpoint.socketFace;
if (!socketByFace.Add(socketKey))
{
messages.Add(new WorldValidationMessage(WorldValidationSeverity.Error,
"More than one JEndpointSocket exists on the same tile face.", endpoint.id));
}
var adjacent = Adjacent(endpoint.worldTile, endpoint.socketFace);
if (stage.ContainsTile(adjacent))
{
messages.Add(new WorldValidationMessage(WorldValidationSeverity.Error,
"JEndpointSocket is placed on an internal Stage face.", endpoint.id));
}
}
else
{
var key = TileKey(stage.worldLayer, endpoint.worldTile);
if (nonSocketByTile.TryGetValue(key, out var existingKind) && existingKind != endpoint.kind)
{
messages.Add(new WorldValidationMessage(WorldValidationSeverity.Error,
"JEndpointAnchor and JEndpointLift cannot coexist on the same WorldTile.", endpoint.id));
}
else
{
nonSocketByTile[key] = endpoint.kind;
}
}
}
}
private static void ValidateConnections(WorldLayoutSO layout, List<WorldValidationMessage> messages)
{
var endpointConnectionCounts = new Dictionary<string, int>();
foreach (var connection in layout.connections)
{
if (connection is null)
{
continue;
}
var endpointA = layout.FindEndpoint(connection.endpointAId);
var endpointB = layout.FindEndpoint(connection.endpointBId);
if (endpointA is null || endpointB is null)
{
messages.Add(new WorldValidationMessage(WorldValidationSeverity.Error,
"JConnection references a missing endpoint.", connection.id));
continue;
}
if (endpointA.id == endpointB.id)
{
messages.Add(new WorldValidationMessage(WorldValidationSeverity.Error,
"JConnection links an endpoint to itself.", connection.id));
}
if (endpointA.kind != endpointB.kind)
{
messages.Add(new WorldValidationMessage(WorldValidationSeverity.Error,
"JConnection links incompatible endpoint types.", connection.id));
}
Increment(endpointConnectionCounts, endpointA.id);
Increment(endpointConnectionCounts, endpointB.id);
ValidateConnectionGeometry(layout, connection, endpointA, endpointB, messages);
}
foreach (var endpoint in layout.endpoints)
{
if (endpoint is null)
{
continue;
}
endpointConnectionCounts.TryGetValue(endpoint.id, out var count);
switch (endpoint.kind)
{
case JEndpointKind.Socket when count > 1:
messages.Add(new WorldValidationMessage(WorldValidationSeverity.Error,
"JEndpointSocket has more than one JConnection.", endpoint.id));
break;
case JEndpointKind.Lift when count > 2:
messages.Add(new WorldValidationMessage(WorldValidationSeverity.Error,
"JEndpointLift has more than two JConnections.", endpoint.id));
break;
}
if (count == 0)
{
messages.Add(new WorldValidationMessage(endpoint.required
? WorldValidationSeverity.Error
: WorldValidationSeverity.Warning,
endpoint.required
? "Required JEndpoint has no JConnection."
: "Optional JEndpoint has no JConnection.",
endpoint.id));
}
}
}
private static void ValidateConnectionGeometry(
WorldLayoutSO layout,
WorldConnectionAuthoring connection,
WorldEndpointAuthoring endpointA,
WorldEndpointAuthoring endpointB,
List<WorldValidationMessage> messages)
{
if (endpointA.kind == JEndpointKind.Socket && endpointB.kind == JEndpointKind.Socket)
{
if (!AreOpposite(endpointA.socketFace, endpointB.socketFace) ||
!IsAdjacentForFaces(endpointA.worldTile, endpointA.socketFace, endpointB.worldTile))
{
messages.Add(new WorldValidationMessage(WorldValidationSeverity.Error,
"JEndpointSocket connections must use opposite faces on adjacent WorldTiles.", connection.id));
}
}
if (endpointA.kind == JEndpointKind.Lift && endpointB.kind == JEndpointKind.Lift)
{
var stageA = layout.FindStage(endpointA.stageId);
var stageB = layout.FindStage(endpointB.stageId);
if (endpointA.worldTile.x != endpointB.worldTile.x || endpointA.worldTile.y != endpointB.worldTile.y)
{
messages.Add(new WorldValidationMessage(WorldValidationSeverity.Error,
"JEndpointLift connections must be aligned on the same 2D WorldTile coordinate.",
connection.id));
}
if (stageA != null && stageB != null &&
Mathf.Abs(stageA.worldLayer - stageB.worldLayer) != 1)
{
messages.Add(new WorldValidationMessage(WorldValidationSeverity.Error,
"JEndpointLift connections must connect adjacent WorldLayers.", connection.id));
}
}
}
private static void Increment(Dictionary<string, int> counts, string key)
{
if (string.IsNullOrEmpty(key))
{
return;
}
counts.TryGetValue(key, out var count);
counts[key] = count + 1;
}
private static string TileKey(int layer, JWorldTile tile)
{
return layer + ":" + tile.x + ":" + tile.y;
}
private static JWorldTile Adjacent(JWorldTile tile, JSocketFace face)
{
return face switch
{
JSocketFace.North => new JWorldTile(tile.x, tile.y - 1),
JSocketFace.South => new JWorldTile(tile.x, tile.y + 1),
JSocketFace.East => new JWorldTile(tile.x + 1, tile.y),
_ => new JWorldTile(tile.x - 1, tile.y)
};
}
private static bool AreOpposite(JSocketFace a, JSocketFace b)
{
return (a == JSocketFace.North && b == JSocketFace.South) ||
(a == JSocketFace.South && b == JSocketFace.North) ||
(a == JSocketFace.East && b == JSocketFace.West) ||
(a == JSocketFace.West && b == JSocketFace.East);
}
private static bool IsAdjacentForFaces(JWorldTile a, JSocketFace faceA, JWorldTile b)
{
var expected = Adjacent(a, faceA);
return expected.x == b.x && expected.y == b.y;
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 86f52d9a97c44cb1b8593c99d411f5b1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 817967f2b72043129f5018488de4c53d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,25 +0,0 @@
using System;
namespace Editor.GON.WorldEditor
{
public enum WorldValidationSeverity
{
Warning = 0,
Error = 1
}
[Serializable]
public sealed class WorldValidationMessage
{
public WorldValidationSeverity severity;
public string message;
public string objectId;
public WorldValidationMessage(WorldValidationSeverity severity, string message, string objectId = null)
{
this.severity = severity;
this.message = message;
this.objectId = objectId;
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: e037255368a041d491478b61b7e0e75e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 93fdd19587ea435285157d73a25958f0
timeCreated: 1781500522

View File

@ -5,7 +5,7 @@ using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Editor.GON.StageEditor.Baker
namespace Editor.GardensStageEditor.Baker
{
namespace Editor.GardensStageEditor
{

View File

@ -7,7 +7,7 @@ using Intrepid.Gameplay.Prototyping;
using UnityEditor;
using UnityEngine;
namespace Editor.GON.StageEditor.Baker
namespace Editor.GardensStageEditor.Baker
{
public struct StageBuilderRequest
{

View File

@ -1,7 +1,7 @@
using System.Collections.Generic;
using UnityEngine;
namespace Editor.GON.StageEditor.Baker
namespace Editor.GardensStageEditor.Baker
{
public static class StageBuilderHelpers
{

View File

@ -2,7 +2,7 @@ using Intrepid.Gameplay.Prototyping;
using Intrepid.Utilities;
using UnityEngine;
namespace Editor.GON.StageEditor
namespace Editor.GardensStageEditor
{
[CreateAssetMenu(fileName = "New Prefab Table", menuName = "GON/Editor/Prefab Table")]
public class PrefabTable : ScriptableObject

View File

@ -3,7 +3,7 @@ using System.Collections.Generic;
using Intrepid.Utilities;
using UnityEngine;
namespace Editor.GON.StageEditor
namespace Editor.GardensStageEditor
{
public enum StageLogicalLayer
{

View File

@ -1,7 +1,7 @@
using Intrepid.Diagnostics;
using UnityEngine;
namespace Editor.GON.StageEditor
namespace Editor.GardensStageEditor
{
internal static class StageEditorColors
{

View File

@ -1,6 +1,7 @@
using System;
using Intrepid.Gameplay.Prototyping;
namespace Editor.GON.StageEditor
namespace Editor.GardensStageEditor
{
public sealed class StageEditorState
{

View File

@ -1,13 +1,13 @@
using System;
using System.Collections.Generic;
using System.IO;
using Editor.GON.StageEditor.Baker.Editor.GardensStageEditor;
using Editor.GardensStageEditor.Baker.Editor.GardensStageEditor;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
namespace Editor.GON.StageEditor
namespace Editor.GardensStageEditor
{
public sealed class StageEditorWindow : EditorWindow
{

View File

@ -1,7 +1,7 @@
using UnityEngine;
using UnityEngine.UIElements;
namespace Editor.GON.StageEditor
namespace Editor.GardensStageEditor
{
public sealed class StageInspectorPanel : VisualElement
{

View File

@ -2,7 +2,7 @@ using System;
using UnityEngine;
using UnityEngine.UIElements;
namespace Editor.GON.StageEditor
namespace Editor.GardensStageEditor
{
public sealed class StageMapCanvas : VisualElement
{

View File

@ -1,7 +1,7 @@
using System.Collections.Generic;
using UnityEngine;
namespace Editor.GON.StageEditor
namespace Editor.GardensStageEditor
{
public sealed class StagePaletteEntry
{

View File

@ -2,7 +2,7 @@ using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
namespace Editor.GON.StageEditor
namespace Editor.GardensStageEditor
{
public sealed class StagePalettePanel : VisualElement
{

View File

@ -1,7 +1,7 @@
using System.Collections.Generic;
using System.Text;
namespace Editor.GON.StageEditor
namespace Editor.GardensStageEditor
{
public enum StageValidationSeverity
{

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 4d17ea30ca5144a099051de0e6b69242
timeCreated: 1781870139

View File

@ -1,33 +0,0 @@
using System;
namespace Intrepid.Gameplay.Journey
{
[Serializable]
public sealed class JConnection
{
public string id;
public string endpointAId;
public string endpointBId;
public JConnectionDirectionality directionality = JConnectionDirectionality.Bidirectional;
public bool ContainsEndpoint(string endpointId)
{
return endpointAId == endpointId || endpointBId == endpointId;
}
public string GetOtherEndpointId(string endpointId)
{
if (endpointAId == endpointId)
{
return endpointBId;
}
if (endpointBId == endpointId)
{
return endpointAId;
}
return null;
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 9c1fd63a9fe84c8fb09bb9c2e804ec8e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,21 +0,0 @@
using System;
namespace Intrepid.Gameplay.Journey
{
[Serializable]
public sealed class JEndpoint
{
public string id;
public string stageId;
public JWorldTile worldTile;
public JEndpointKind kind;
public bool required;
// Used when kind is Socket. Ignored by Anchor and Lift endpoints.
public JSocketFace socketFace;
public bool IsSocket => kind == JEndpointKind.Socket;
public bool IsAnchor => kind == JEndpointKind.Anchor;
public bool IsLift => kind == JEndpointKind.Lift;
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 71c2f06702924f18b413f79bb35f9af3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,102 +0,0 @@
using System.Collections.Generic;
using UnityEngine;
namespace Intrepid.Gameplay.Journey
{
[CreateAssetMenu(menuName = "GON/Journey/J Graph", fileName = "JGraph")]
public sealed class JGraphSO : ScriptableObject
{
public string sourceWorldLayoutId;
public string publishedAt;
public int schemaVersion = 1;
public string contentHash;
public List<JStage> stages = new();
public List<JEndpoint> endpoints = new();
public List<JConnection> connections = new();
public void EnsureDefaults()
{
stages ??= new List<JStage>();
endpoints ??= new List<JEndpoint>();
connections ??= new List<JConnection>();
foreach (var stage in stages)
{
stage?.EnsureDefaults();
}
}
public JStage FindStage(string stageId)
{
EnsureDefaults();
foreach (var stage in stages)
{
if (stage != null && stage.id == stageId)
{
return stage;
}
}
return null;
}
public JEndpoint FindEndpoint(string endpointId)
{
EnsureDefaults();
foreach (var endpoint in endpoints)
{
if (endpoint != null && endpoint.id == endpointId)
{
return endpoint;
}
}
return null;
}
public JConnection FindConnection(string connectionId)
{
EnsureDefaults();
foreach (var connection in connections)
{
if (connection != null && connection.id == connectionId)
{
return connection;
}
}
return null;
}
public List<JEndpoint> GetEndpointsForStage(string stageId)
{
EnsureDefaults();
var result = new List<JEndpoint>();
foreach (var endpoint in endpoints)
{
if (endpoint != null && endpoint.stageId == stageId)
{
result.Add(endpoint);
}
}
return result;
}
public List<JConnection> GetConnectionsForEndpoint(string endpointId)
{
EnsureDefaults();
var result = new List<JConnection>();
foreach (var connection in connections)
{
if (connection != null && connection.ContainsEndpoint(endpointId))
{
result.Add(connection);
}
}
return result;
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: fcb661d795ed4088af4ae73e4253f98b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,21 +0,0 @@
using System;
using System.Collections.Generic;
namespace Intrepid.Gameplay.Journey
{
[Serializable]
public sealed class JStage
{
public string id;
public string displayName;
public int worldLayer;
public List<JWorldTile> occupiedTiles = new();
public List<string> tags = new();
public void EnsureDefaults()
{
occupiedTiles ??= new List<JWorldTile>();
tags ??= new List<string>();
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 5dbe6985a3384e0190ac342eb49eb4d9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,39 +0,0 @@
using System;
namespace Intrepid.Gameplay.Journey
{
public enum JEndpointKind
{
Socket = 0,
Anchor = 1,
Lift = 2
}
public enum JSocketFace
{
North = 0,
South = 1,
East = 2,
West = 3
}
public enum JConnectionDirectionality
{
Bidirectional = 0,
AToB = 1,
BToA = 2
}
[Serializable]
public struct JWorldTile
{
public int x;
public int y;
public JWorldTile(int x, int y)
{
this.x = x;
this.y = y;
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 6fd7e60621d443e3a2f02fbb9ffcc801
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,2 +0,0 @@
{"type":"meta","next_id":{"DSC":2,"AGD":2,"DEC":2,"PLN":5,"LSN":2,"CLSN":1}}
{"type":"discussion","id":"DSC-0001","status":"done","ticket":"world-editor-journey-spec","title":"World Editor / Journey Spec","created_at":"2026-06-19","updated_at":"2026-06-20","tags":["world-editor","journey","stage-contract","architecture"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0001","file":"discussion/lessons/DSC-0001-world-editor-journey-spec/LSN-0001-editor-authoring-runtime-publication.md","status":"done","created_at":"2026-06-20","updated_at":"2026-06-20"}]}

View File

@ -1,52 +0,0 @@
---
id: LSN-0001
ticket: world-editor-journey-spec
title: Separate editor authoring data from runtime Journey publication
created: 2026-06-20
tags:
- world-editor
- journey
- unity
- scriptableobjects
---
## Context
The Gardens of Nil World Editor needs to organize the macro-world while the Stage Editor owns internal stage content. The first Journey implementation had to support editor iteration without letting unfinished authoring data leak into gameplay playtests.
The resulting model uses two ScriptableObject layers:
- `WorldLayoutSO` is editor-only authoring data for stages, footprints, endpoints, and connections.
- `JGraphSO` is the published runtime Journey graph consumed by gameplay code.
## Key Decisions
### World Editor / Journey V1
**What:** Keep world authoring data under editor code and publish a separate runtime-safe Journey graph through an explicit commit action.
**Why:** Designers need to edit incomplete worlds without changing the runtime contract used by playtests. Gameplay code should depend on stable Journey data and must not reference editor-only types.
**Trade-offs:** The model requires a publication step and validation rules between authoring and runtime data. In exchange, it avoids coupling runtime traversal to mutable editor state and keeps Stage bake ownership separate from world topology authoring.
## Patterns and Algorithms
- Treat editor assets as work-in-progress sources of truth, not runtime APIs.
- Publish runtime data through a deliberate commit action after validation.
- Keep generated runtime records plain and queryable: `JStage`, `JEndpoint`, `JConnection`, and `JGraphSO`.
- Use stable string ids for cross-editor contracts because Unity object references are not enough to bridge World Editor, Stage Editor, and runtime data safely.
- Model physical transition space as stages, not as connections. `JConnection` remains logical and never owns space.
## Pitfalls
- If runtime code reads editor-only data directly, playtests can accidentally depend on incomplete authoring changes.
- If connections own physical space, the boundary between World Editor and Stage Editor becomes ambiguous.
- If validation does not prune stale authoring records, deleted or moved stages can leave invisible data that still affects Journey publication.
- If StageDefinition becomes the runtime Journey contract, the Stage Editor and runtime traversal become coupled too early.
## Takeaways
- Use `WorldLayoutSO` for authoring and `JGraphSO` for runtime Journey.
- Keep commit/publish explicit and validation-driven.
- Keep Journey runtime code outside `Assets/Scripts/Editor`.
- Preserve Stage Editor ownership of scene geometry and bake output.

View File

@ -1,229 +0,0 @@
# World Editor / Journey V1 Specification
Source decision: `discussion/workflow/decisions/DEC-0001-world-editor-journey-v1.md`
## Purpose
The World Editor defines the macro-world contract for Gardens of Nil. It does not edit internal Stage content. Journey is the runtime-readable logical graph published from the World Editor.
This specification defines the V1 authoring and runtime boundary.
## Ownership Boundary
The World Editor SHALL own:
- macro WorldLayers;
- WorldTile occupancy;
- logical Stage placement;
- Journey endpoints;
- Journey connections;
- commit-time validation;
- publication of logical Journey data.
The Stage Editor SHALL own:
- internal Stage geometry;
- floors inside a Stage;
- Stage props;
- Stage objects;
- Stage scene generation;
- Stage bake.
The World Editor commit MUST NOT create Stage scenes, Stage geometry, internal Stage objects, or baked Stage content.
## Authoring Asset
`WorldLayoutSO` is the editor-only authoring ScriptableObject for the World Editor.
`WorldLayoutSO` MUST contain:
- a stable layout id;
- `worldTileSize`, defaulting to `24m`;
- `worldLayerHeight`, defaulting to `24m`;
- authoring Stage records;
- occupied WorldTiles per Stage;
- authoring endpoint records;
- authoring connection records;
- editor metadata.
`worldTileSize` and `worldLayerHeight` MUST be configurable. V1 defaults are `24m`.
`WorldLayoutSO` MAY reference editor-only Stage authoring data, including `StageDefinition`, but runtime code MUST NOT consume `WorldLayoutSO`.
## Runtime Asset
`JGraphSO` is the runtime Journey ScriptableObject.
Journey elements MUST use the `J` prefix. Journey ScriptableObjects MUST also use the `SO` suffix.
`JGraphSO` MUST contain:
- `sourceWorldLayoutId`;
- `publishedAt`;
- `schemaVersion`;
- optional `contentHash`;
- `JStage` records;
- `JEndpoint` records;
- `JConnection` records.
Runtime Journey code MUST consume `JGraphSO`. Runtime Journey code MUST NOT depend on `WorldLayoutSO`, `StageDefinition`, or any type under `Assets/Scripts/Editor`.
## World Model
The World Editor is 2D per `WorldLayer`.
A Stage:
- belongs to exactly one `WorldLayer`;
- occupies a set of WorldTiles;
- MUST NOT overlap another Stage on the same WorldLayer tile.
A WorldTile can be occupied by at most one Stage within a WorldLayer.
All physical space MUST belong to a Stage. `JConnection` MUST NOT own physical space. If a corridor, transition room, long lift, loading mask, or similar physical traversal space is needed, it MUST be represented as a Stage.
## JStage
`JStage` is the runtime logical representation of a Stage inside `JGraphSO`.
`JStage` MUST have a stable GUID/string id. Tags MAY be used for classification and lookup, but tags MUST NOT replace stable identity.
`JStage` is not Stage scene generation data and is not Stage bake data.
## Endpoints
Initial endpoint types are:
- `JEndpointSocket`;
- `JEndpointAnchor`;
- `JEndpointLift`.
Every endpoint MUST have:
- a stable GUID/string id;
- an owning `JStage` id;
- a WorldTile coordinate;
- an endpoint type;
- a required/optional flag.
### JEndpointSocket
`JEndpointSocket` represents a physical side connection on a free tile face.
It MUST:
- exist only on a free face of a Stage WorldTile;
- use one of `North`, `South`, `East`, or `West`;
- accept at most one `JConnection`;
- connect only to another `JEndpointSocket`.
Socket-to-socket physical connections MUST connect opposite faces on adjacent WorldTiles.
### JEndpointAnchor
`JEndpointAnchor` represents an abstract travel point such as a portal, hub, or fast-travel anchor.
It MAY have multiple `JConnections`. Runtime destination selection for multi-connection anchors is outside V1.
### JEndpointLift
`JEndpointLift` represents macro vertical travel between WorldLayers.
It MUST:
- accept exactly one `JConnection` in V1;
- connect only to another `JEndpointLift`;
- align on the same 2D WorldTile coordinate as its paired lift;
- connect across different WorldLayers.
## Endpoint Coexistence
A WorldTile MAY contain sockets and one non-socket endpoint.
`JEndpointAnchor` and `JEndpointLift` MUST NOT coexist on the same WorldTile.
Sockets MAY coexist with anchors or lifts.
## Connections
`JConnection` is a logical edge between exactly two endpoints.
`JConnection` MUST:
- have a stable GUID/string id;
- reference endpoint A by id;
- reference endpoint B by id;
- connect endpoints of compatible type;
- not own physical space.
Endpoint compatibility in V1 is same-type only:
- socket to socket;
- anchor to anchor;
- lift to lift.
Directionality MAY be represented in V1. Concrete requirement modeling and loading hints are outside V1, but `JConnection` SHOULD remain extensible so those fields can be added later.
## Commit Flow
The World Editor SHALL publish Journey data through an explicit commit action.
The commit action MUST:
- validate `WorldLayoutSO`;
- abort if commit-blocking errors exist;
- allow publication when only warnings exist;
- create or update `JGraphSO`;
- write `JStage`, `JEndpoint`, and `JConnection` records;
- write publication metadata.
The commit action MUST NOT:
- generate Stage scenes;
- generate Stage geometry;
- generate internal Stage objects;
- run Stage bake;
- write Stage Editor content.
## Validation
Commit-blocking errors include:
- same-layer WorldTile overlap between Stages;
- connection referencing a missing endpoint;
- connection linking an endpoint to itself;
- incompatible endpoint types;
- socket with more than one connection;
- socket on an internal Stage face;
- socket connected to a non-opposite or non-adjacent socket;
- anchor and lift on the same WorldTile;
- lift connected to a lift with a different 2D coordinate;
- required endpoint without connection.
Warnings include:
- optional endpoint without connection;
- Stage without endpoints;
- authoring metadata missing from non-critical fields.
## Editor Technology
World Editor V1 SHALL use the same window technology as the existing Stage Editor:
- `EditorWindow`;
- UI Toolkit via `UnityEngine.UIElements` and `UnityEditor.UIElements`;
- custom canvas based on `VisualElement.generateVisualContent`.
Stage Editor integration remains manual in V1.
## V1 Non-Goals
V1 does not include:
- loading hints;
- streaming policies;
- concrete `JRequirement` structures;
- runtime destination selection UI or logic for multi-connection anchors;
- deep Stage Editor marker sync;
- automatic Stage Editor opening from the World Editor;
- unified World/Stage Editor shell.