feat(players): NPC disposition types and launch-with-players session tokens

Add Hostile/Neutral/Friendly ring types with session-only inactive overrides on control, plus launch-with-players flow and live player tokens on the map.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-07-30 17:11:01 +08:00
parent 101f595bac
commit cc50e64e21
38 changed files with 1803 additions and 59 deletions
+11 -4
View File
@@ -245,6 +245,7 @@ void test('selectNpcsByIds / buildPartialExportProject: explicit NPCs, relations
y: 0,
groupId: gChild,
ringColor: '#c9a227',
disposition: 'neutral',
imageOffset: { x: 0, y: 0 },
imageScale: 1,
},
@@ -257,6 +258,7 @@ void test('selectNpcsByIds / buildPartialExportProject: explicit NPCs, relations
y: 0,
groupId: gChild,
ringColor: '#c9a227',
disposition: 'neutral',
imageOffset: { x: 0, y: 0 },
imageScale: 1,
},
@@ -269,6 +271,7 @@ void test('selectNpcsByIds / buildPartialExportProject: explicit NPCs, relations
y: 0,
groupId: asNpcGroupId('g_other'),
ringColor: '#c9a227',
disposition: 'neutral',
imageOffset: { x: 0, y: 0 },
imageScale: 1,
},
@@ -329,9 +332,9 @@ void test('buildPartialExportProject: keeps npcTokens only for exported NPCs', (
[sceneId]: {
...scene('s1', 'Start'),
npcTokens: [
{ id: asSceneNpcTokenId('nt1'), npcId: n1, nx: 0.2, ny: 0.3, sizeN: 0.1 },
{ id: asSceneNpcTokenId('nt2'), npcId: n2, nx: 0.5, ny: 0.5, sizeN: 0.1 },
{ id: asSceneNpcTokenId('nt3'), npcId: asNpcId('missing'), nx: 0.1, ny: 0.1, sizeN: 0.1 },
{ id: asSceneNpcTokenId('nt1'), npcId: n1, nx: 0.2, ny: 0.3, sizeN: 0.1, disposition: 'neutral' },
{ id: asSceneNpcTokenId('nt2'), npcId: n2, nx: 0.5, ny: 0.5, sizeN: 0.1, disposition: 'hostile' },
{ id: asSceneNpcTokenId('nt3'), npcId: asNpcId('missing'), nx: 0.1, ny: 0.1, sizeN: 0.1, disposition: 'neutral' },
],
},
},
@@ -358,6 +361,7 @@ void test('buildPartialExportProject: keeps npcTokens only for exported NPCs', (
y: 0,
groupId: null,
ringColor: '#c9a227',
disposition: 'neutral',
imageOffset: { x: 0, y: 0 },
imageScale: 1,
},
@@ -370,6 +374,7 @@ void test('buildPartialExportProject: keeps npcTokens only for exported NPCs', (
y: 0,
groupId: null,
ringColor: '#c9a227',
disposition: 'neutral',
imageOffset: { x: 0, y: 0 },
imageScale: 1,
},
@@ -395,7 +400,7 @@ void test('mergeStorylinesIntoProject: remaps npcTokens to created NPC ids', ()
[asSceneId('s1')]: {
...scene('s1', 'Side'),
npcTokens: [
{ id: asSceneNpcTokenId('nt1'), npcId: sourceNpcId, nx: 0.4, ny: 0.6, sizeN: 0.12 },
{ id: asSceneNpcTokenId('nt1'), npcId: sourceNpcId, nx: 0.4, ny: 0.6, sizeN: 0.12, disposition: 'friendly' },
],
},
},
@@ -422,6 +427,7 @@ void test('mergeStorylinesIntoProject: remaps npcTokens to created NPC ids', ()
y: 0,
groupId: null,
ringColor: '#aabbcc',
disposition: 'neutral',
imageOffset: { x: 0.1, y: -0.1 },
imageScale: 1,
},
@@ -480,6 +486,7 @@ void test('mergeStorylinesIntoProject: imports all NPCs from export bundle', ()
y: 0,
groupId: null,
ringColor: '#c9a227',
disposition: 'neutral',
imageOffset: { x: 0, y: 0 },
imageScale: 1,
},
@@ -697,6 +697,7 @@ export function mergeStorylinesIntoProject(
y: n.y,
groupId: mappedGroupId && npcGroups.some((g) => g.id === mappedGroupId) ? mappedGroupId : null,
ringColor: n.ringColor ?? '#c9a227',
disposition: n.disposition === 'hostile' || n.disposition === 'friendly' ? n.disposition : 'neutral',
imageOffset: n.imageOffset ?? { x: 0, y: 0 },
imageScale: typeof n.imageScale === 'number' && Number.isFinite(n.imageScale) ? n.imageScale : 1,
});