fix: encounter data assignment

This commit is contained in:
hypercross 2026-04-14 14:35:23 +08:00
parent 204198b10f
commit 4fbd65e98c
2 changed files with 58 additions and 19 deletions

View File

@ -20,12 +20,12 @@ function buildEncounterIndex(): Map<string, EncounterDesert[]> {
/** Map from MapNodeType to encounter type key */ /** Map from MapNodeType to encounter type key */
const NODE_TYPE_TO_ENCOUNTER: Partial<Record<MapNodeType, string>> = { const NODE_TYPE_TO_ENCOUNTER: Partial<Record<MapNodeType, string>> = {
[MapNodeType.Minion]: 'enemy', [MapNodeType.Minion]: 'minion',
[MapNodeType.Elite]: 'elite', [MapNodeType.Elite]: 'elite',
[MapNodeType.Event]: 'event', [MapNodeType.Event]: 'event',
[MapNodeType.Camp]: 'shelter', [MapNodeType.Camp]: 'camp',
[MapNodeType.Shop]: 'npc', [MapNodeType.Shop]: 'shop',
[MapNodeType.Curio]: 'shelter', [MapNodeType.Curio]: 'curio',
}; };
/** Default map generation configuration */ /** Default map generation configuration */
@ -125,9 +125,15 @@ export function generatePointCrawlMap(seed?: number): PointCrawlMap {
const nodeIds: string[] = []; const nodeIds: string[] = [];
const layerNodes: MapNode[] = []; const layerNodes: MapNode[] = [];
// Pre-generate settlement types if this is a settlement layer
let settlementTypes: MapNodeType[] | undefined;
if (structure.layerType === MapLayerType.Settlement) {
settlementTypes = generateSettlementTypes(rng);
}
for (let j = 0; j < structure.count; j++) { for (let j = 0; j < structure.count; j++) {
const id = `node-${i}-${j}`; const id = `node-${i}-${j}`;
const type = resolveNodeType(structure.layerType, j, structure.count, rng, wildPairTypes.get(i), j); const type = resolveNodeType(structure.layerType, j, structure.count, rng, wildPairTypes.get(i), j, settlementTypes, j);
const encounter = pickEncounterForNode(type, rng); const encounter = pickEncounterForNode(type, rng);
const node: MapNode = { const node: MapNode = {
id, id,
@ -175,7 +181,9 @@ function resolveNodeType(
_layerCount: number, _layerCount: number,
rng: RNG, rng: RNG,
preGeneratedTypes?: MapNodeType[], preGeneratedTypes?: MapNodeType[],
nodeIndex?: number nodeIndex?: number,
settlementTypes?: MapNodeType[],
settlementIndex?: number
): MapNodeType { ): MapNodeType {
switch (layerType) { switch (layerType) {
case 'start': case 'start':
@ -189,8 +197,11 @@ function resolveNodeType(
} }
return pickWildNodeType(rng); return pickWildNodeType(rng);
case MapLayerType.Settlement: case MapLayerType.Settlement:
// This will be overridden by assignSettlementTypes // Use pre-generated settlement types if available
return MapNodeType.Camp; // placeholder if (settlementTypes && settlementIndex !== undefined) {
return settlementTypes[settlementIndex];
}
return MapNodeType.Camp; // fallback
default: default:
return MapNodeType.Minion; return MapNodeType.Minion;
} }
@ -295,9 +306,22 @@ function generateOptimalWildPair(
return [bestLayer1, bestLayer2]; return [bestLayer1, bestLayer2];
} }
/**
* Generates settlement node types ensuring at least 1 of each: camp, shop, curio.
* The 4th node is randomly chosen from the three.
* Returns shuffled array of 4 node types.
*/
function generateSettlementTypes(rng: RNG): MapNodeType[] {
const requiredTypes = [MapNodeType.Camp, MapNodeType.Shop, MapNodeType.Curio];
const randomType = requiredTypes[rng.nextInt(3)];
const types = [...requiredTypes, randomType];
return fisherYatesShuffle(types, rng);
}
/** /**
* Assigns settlement node types ensuring at least 1 of each: camp, shop, curio. * Assigns settlement node types ensuring at least 1 of each: camp, shop, curio.
* The 4th node is randomly chosen from the three. * The 4th node is randomly chosen from the three.
* @deprecated Use generateSettlementTypes() during node creation instead.
*/ */
function assignSettlementTypes(nodeIds: string[], nodes: MapNode[], rng: RNG): void { function assignSettlementTypes(nodeIds: string[], nodes: MapNode[], rng: RNG): void {
// Shuffle node order to randomize which position gets which type // Shuffle node order to randomize which position gets which type
@ -323,10 +347,8 @@ function generateLayerEdges(
nodes: Map<string, MapNode>, nodes: Map<string, MapNode>,
rng: RNG rng: RNG
): void { ): void {
// Assign settlement types when creating settlement layer // Settlement types are now pre-generated during node creation
if (targetLayer.layerType === MapLayerType.Settlement) { // No need to assign them here anymore
assignSettlementTypes(targetLayer.nodeIds, targetLayer.nodes, rng);
}
const sourceType = sourceLayer.layerType; const sourceType = sourceLayer.layerType;
const targetType = targetLayer.layerType; const targetType = targetLayer.layerType;

View File

@ -287,19 +287,36 @@ describe('generatePointCrawlMap', () => {
} }
}); });
it('should assign encounters to nodes', () => { it('should assign encounters to all non-Start/End nodes', () => {
const map = generatePointCrawlMap(456); const map = generatePointCrawlMap(456);
let nodesWithEncounter = 0;
for (const node of map.nodes.values()) { for (const node of map.nodes.values()) {
if (node.encounter) { if (node.type === MapNodeType.Start || node.type === MapNodeType.End) {
nodesWithEncounter++; // Start and End nodes should not have encounters
expect(node.encounter.name).toBeTruthy(); expect(node.encounter).toBeUndefined();
expect(node.encounter.description).toBeTruthy(); } else {
// All other nodes (minion/elite/event/camp/shop/curio) must have encounters
expect(node.encounter, `Node ${node.id} (${node.type}) should have encounter data`).toBeDefined();
expect(node.encounter!.name).toBeTruthy();
expect(node.encounter!.description).toBeTruthy();
} }
} }
});
expect(nodesWithEncounter).toBeGreaterThan(0); it('should assign encounters to all nodes across multiple seeds', () => {
// Test multiple seeds to ensure no random failure
for (let seed = 0; seed < 20; seed++) {
const map = generatePointCrawlMap(seed);
for (const node of map.nodes.values()) {
if (node.type === MapNodeType.Start || node.type === MapNodeType.End) {
continue;
}
expect(node.encounter, `Seed ${seed}: Node ${node.id} (${node.type}) missing encounter`).toBeDefined();
expect(node.encounter!.name).toBeTruthy();
expect(node.encounter!.description).toBeTruthy();
}
}
}); });
it('should minimize same-layer repetitions in wild layer pairs', () => { it('should minimize same-layer repetitions in wild layer pairs', () => {