Route faction job boards to their own description pools

This commit is contained in:
Brian Fertig 2026-09-30 20:35:32 -06:00
parent 57024084f4
commit cb7033861f
1 changed files with 42 additions and 4 deletions

View File

@ -1734,6 +1734,43 @@ export function parseSideQuestId(id) {
return { systemId: m[1], worldKey: m[2].toLowerCase(), category: cat, slot: Math.floor(Number(m[4])) };
}
/**
* The COPY POOL a posting draws from (data/sidequests.json → descriptions).
*
* THREE-DEEP FALLBACK, fixed order (a board always reads something):
* 1. descriptions.faction.<factionId>.<category> — the FACTION'S OWN voice
* (apex / mindweave / zenith). Each of the nine pools is written in that
* faction's doctrine and names the rival it will actually fight through
* the {enemy} placeholder (the posting's seeded enemyFaction).
* 2. descriptions.faction.<category> — the shared war-voice pool: the
* documented fallback for a faction with no pool of its own (a new
* power, or a hand-edited data file).
* 3. descriptions.neutral.<category> — the neutral voice, last resort.
*
* A pool that stands but is not an array (a typo in the data) falls through
* to the next link rather than posting nothing. Pure in (context, category,
* faction): no RNG, so it never disturbs the posting's seeded draw.
*
* @param {object} ctx postingContext() shape ({ context: 'neutral'|'faction', … })
* @param {string} cat the category id
* @param {string|null} [factionId] the board's faction id (a faction board)
* @returns {object[]} the variant pool ([] = no copy at all)
*/
function descriptionPool(ctx, cat, factionId = null) {
const fallbacks = [];
const own = String(factionId ?? '').trim();
if (ctx?.context === 'faction' && own !== '') {
fallbacks.push(`sidequests.descriptions.faction.${own}.${cat}`);
}
fallbacks.push(`sidequests.descriptions.faction.${cat}`);
fallbacks.push(`sidequests.descriptions.neutral.${cat}`);
for (const path of fallbacks) {
const pool = config.get(path, null);
if (Array.isArray(pool)) return pool;
}
return [];
}
/** One posting from its pool variant + reward roll (null on a bad pool entry). */
function buildPosting(seed, rec, ctx, cat, slot, r, galaxy = null, usedDossier = null, world = 'p1') {
const meta = sideQuestCategoryMeta(cat);
@ -1772,10 +1809,11 @@ function buildPosting(seed, rec, ctx, cat, slot, r, galaxy = null, usedDossier =
if (!escortDestinationFor(seed, postingId, rec.id, galaxy)) return null;
}
const poolPath = `sidequests.descriptions.${ctx.context}.${cat}`;
let pool = config.get(poolPath, null);
if (!Array.isArray(pool)) pool = config.get(`sidequests.descriptions.neutral.${cat}`, null); // the neutral voice as fallback
pool = Array.isArray(pool) ? pool : [];
// The copy pool: this faction's own pool when it has one (the three-deep
// fallback above), so an Apex board reads Apex and a Zenith board reads
// Zenith. The draw rides the posting's own seeded stream, exactly as the
// shared pool did — same seed, same slot, same variant index.
const pool = descriptionPool(ctx, cat, factionCtx ? rec.faction : null);
let v = pool.length > 0 ? pool[r.int(0, pool.length - 1)] : null;
// A WANTED LIST never posts the same leader twice on one board: the seeded
// draw stands when it is fresh, otherwise the next entry forward (wrapping)