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 */
const NODE_TYPE_TO_ENCOUNTER: Partial<Record<MapNodeType, string>> = {
[MapNodeType.Minion]: 'enemy',
[MapNodeType.Minion]: 'minion',
[MapNodeType.Elite]: 'elite',
[MapNodeType.Event]: 'event',
[MapNodeType.Camp]: 'shelter',
[MapNodeType.Shop]: 'npc',
[MapNodeType.Curio]: 'shelter',
[MapNodeType.Camp]: 'camp',
[MapNodeType.Shop]: 'shop',
[MapNodeType.Curio]: 'curio',
};
/** Default map generation configuration */
@ -125,9 +125,15 @@ export function generatePointCrawlMap(seed?: number): PointCrawlMap {
const nodeIds: string[] = [];
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++) {
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 node: MapNode = {
id,
@ -175,7 +181,9 @@ function resolveNodeType(
_layerCount: number,
rng: RNG,
preGeneratedTypes?: MapNodeType[],
nodeIndex?: number
nodeIndex?: number,
settlementTypes?: MapNodeType[],
settlementIndex?: number
): MapNodeType {
switch (layerType) {
case 'start':
@ -189,8 +197,11 @@ function resolveNodeType(
}
return pickWildNodeType(rng);
case MapLayerType.Settlement:
// This will be overridden by assignSettlementTypes
return MapNodeType.Camp; // placeholder
// Use pre-generated settlement types if available
if (settlementTypes && settlementIndex !== undefined) {
return settlementTypes[settlementIndex];
}
return MapNodeType.Camp; // fallback
default:
return MapNodeType.Minion;
}
@ -295,9 +306,22 @@ function generateOptimalWildPair(
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.
* 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 {
// Shuffle node order to randomize which position gets which type
@ -323,10 +347,8 @@ function generateLayerEdges(
nodes: Map<string, MapNode>,
rng: RNG
): void {
// Assign settlement types when creating settlement layer
if (targetLayer.layerType === MapLayerType.Settlement) {
assignSettlementTypes(targetLayer.nodeIds, targetLayer.nodes, rng);
}
// Settlement types are now pre-generated during node creation
// No need to assign them here anymore
const sourceType = sourceLayer.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);
let nodesWithEncounter = 0;
for (const node of map.nodes.values()) {
if (node.encounter) {
nodesWithEncounter++;
expect(node.encounter.name).toBeTruthy();
expect(node.encounter.description).toBeTruthy();
if (node.type === MapNodeType.Start || node.type === MapNodeType.End) {
// Start and End nodes should not have encounters
expect(node.encounter).toBeUndefined();
} 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', () => {