init with react+axum+spacetimedb
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
2026-04-26 18:06:23 +08:00
commit cbc27bad4a
20199 changed files with 883714 additions and 0 deletions

View File

@@ -0,0 +1,154 @@
import { describe, expect, it } from 'vitest';
import {
AnimationState,
type Character,
type Encounter,
type GameState,
WorldType,
} from '../types';
import { getMonsterPresetsByWorld } from './hostileNpcPresets';
import { createSceneHostileNpc } from './hostileNpcs';
import { buildInitialNpcState } from './npcInteractions';
import {
hasAutoBattleSceneEncounter,
resolveSceneEncounterPreview,
} from './sceneEncounterPreviews';
function createCharacter(): Character {
return {
id: 'hero',
name: 'Hero',
title: 'Wanderer',
description: 'A reliable test hero.',
backstory: 'Travels the land.',
avatar: '/hero.png',
portrait: '/hero-portrait.png',
assetFolder: 'hero',
assetVariant: 'default',
attributes: {
strength: 10,
agility: 9,
intelligence: 8,
spirit: 7,
},
personality: 'steady',
skills: [],
adventureOpenings: {},
};
}
function createEncounter(): Encounter {
return {
id: 'npc-trader',
kind: 'npc',
npcName: 'Trader Lin',
npcDescription: 'A traveling merchant.',
npcAvatar: 'T',
context: 'merchant',
initialAffinity: 12,
hostile: false,
};
}
function createBaseState(): GameState {
const encounter = createEncounter();
return {
worldType: WorldType.WUXIA,
customWorldProfile: null,
playerCharacter: createCharacter(),
runtimeStats: {
playTimeMs: 0,
lastPlayTickAt: null,
hostileNpcsDefeated: 0,
questsAccepted: 0,
itemsUsed: 0,
scenesTraveled: 0,
},
currentScene: 'Story',
storyHistory: [],
characterChats: {},
animationState: AnimationState.IDLE,
currentEncounter: encounter,
npcInteractionActive: false,
currentScenePreset: {
id: 'scene-1',
name: 'Trail',
description: 'A mountain trail.',
imageSrc: '/trail.png',
connectedSceneIds: [],
npcs: [],
treasureHints: [],
},
sceneHostileNpcs: [],
playerX: 0,
playerOffsetY: 0,
playerFacing: 'right',
playerActionMode: 'idle',
scrollWorld: false,
inBattle: false,
playerHp: 100,
playerMaxHp: 100,
playerMana: 20,
playerMaxMana: 20,
playerSkillCooldowns: {},
activeCombatEffects: [],
playerCurrency: 0,
playerInventory: [],
playerEquipment: {
weapon: null,
armor: null,
relic: null,
},
npcStates: {
[encounter.id!]: {
...buildInitialNpcState(encounter, WorldType.WUXIA),
affinity: -5,
},
},
quests: [],
roster: [],
companions: [],
currentBattleNpcId: null,
currentNpcBattleMode: null,
currentNpcBattleOutcome: null,
sparReturnEncounter: null,
sparPlayerHpBefore: null,
sparPlayerMaxHpBefore: null,
sparStoryHistoryBefore: null,
};
}
describe('sceneEncounterPreviews', () => {
it('treats negative-affinity npc encounters as immediate battles', () => {
const state = createBaseState();
expect(hasAutoBattleSceneEncounter(state)).toBe(true);
const resolved = resolveSceneEncounterPreview(state);
expect(resolved.inBattle).toBe(true);
expect(resolved.currentEncounter).toBeNull();
expect(resolved.currentBattleNpcId).toBe('npc-trader');
expect(resolved.currentNpcBattleMode).toBe('fight');
expect(resolved.sceneHostileNpcs).toHaveLength(1);
expect(resolved.sceneHostileNpcs[0]?.encounter?.npcName).toBe('Trader Lin');
});
it('attaches npc encounter metadata to regular monsters', () => {
const monsterId = getMonsterPresetsByWorld(WorldType.WUXIA)[0]?.id;
if (!monsterId) {
throw new Error('Expected at least one monster preset');
}
const monster = createSceneHostileNpc(WorldType.WUXIA, monsterId);
expect(monster).not.toBeNull();
expect(monster?.encounter?.kind).toBe('npc');
expect(monster?.encounter?.monsterPresetId).toBe(monsterId);
expect(monster?.encounter?.hostile).toBe(true);
expect(monster?.encounter?.initialAffinity).toBe(-40);
});
});