feat(players): app Players library and circular NPC tokens on scenes

Add userData players/teams, scene npcTokens with hex-inscribed sizing, session scale synced to presentation, and Playwright e2e coverage.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-07-30 13:38:35 +08:00
parent a3a03eb9e3
commit 101f595bac
57 changed files with 4176 additions and 317 deletions
+3 -8
View File
@@ -92,11 +92,7 @@ export class TokensStore {
return path.join(this.rootDir, relPath);
}
async upsert(input: {
id?: TokenId | null;
name: string;
filePath?: string | null;
}): Promise<AppToken> {
async upsert(input: { id?: TokenId | null; name: string; filePath?: string | null }): Promise<AppToken> {
await this.ensureLoaded();
const name = input.name.trim();
if (!name) throw new Error('Token name is required');
@@ -166,8 +162,7 @@ export class TokensStore {
}): Promise<{ token: AppToken; remappedFrom: TokenId }> {
await this.ensureLoaded();
let buf = await fs.readFile(input.absFilePath);
const sha256 =
input.sha256 ?? crypto.createHash('sha256').update(buf).digest('hex');
const sha256 = input.sha256 ?? crypto.createHash('sha256').update(buf).digest('hex');
const existingByHash = this.findBySha256(sha256);
if (existingByHash) {
return { token: existingByHash, remappedFrom: input.preferredId };
@@ -262,7 +257,7 @@ export class TokensStore {
preferredId: asTokenId(t.id),
name: typeof t.name === 'string' ? t.name : 'Token',
absFilePath: abs,
sha256: typeof t.sha256 === 'string' ? t.sha256 : undefined,
...(typeof t.sha256 === 'string' ? { sha256: t.sha256 } : {}),
});
remap.set(remappedFrom, token.id);
}