mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-11 21:57:14 +03:00
Split breadth from rating in the challenger and staging pools
Rating grades quality, breadth says whether a world can serve an arbitrary build at all; while they shared one field, the only way to hold a narrow world back was calling it marginal, which made excellent but narrow unrecordable and corrupted the ratings as a calibration signal for the next authoring round. Both axes now exclude independently, either kind of hold keeps its approval for direct briefs, an all-niche tier falls back rather than starving, and stagings honour the same gate with the same fallback. Tests cover the niche exclusion at strength, the fallback parity with marginal-only tiers, and the staging gate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
d3c7b05a3e
commit
ce4dcf9a93
@@ -206,7 +206,13 @@ ${grammar}
|
||||
// returns no staging. Re-rolls exclude every earlier set until the pool runs out.
|
||||
export function selectApprovedStagings({ scope, key, reroll = 0, mode = null, sourceCompositions = null, count = 3 }) {
|
||||
const pool = sourceCompositions ?? requireLocalConcepts().compositions;
|
||||
// Stagings honour the same breadth gate as worlds: a staging too specific to
|
||||
// serve an arbitrary build stays approved for direct briefs and leaves the
|
||||
// challenger pool. Falls back to the full approved set rather than returning
|
||||
// nothing if every approved staging is marked niche.
|
||||
let approved = pool.filter(composition => composition.status === 'approved');
|
||||
const broad = approved.filter(composition => composition.review?.breadth !== 'niche');
|
||||
if (broad.length > 0) approved = broad;
|
||||
if (approved.length === 0) return [];
|
||||
if (mode) {
|
||||
const matching = approved.filter(composition => composition.surface === mode);
|
||||
@@ -271,12 +277,19 @@ export function selectApprovedChallengers({ scope, key, reroll = 0, sourceConcep
|
||||
// graphic systems beside instrument languages and atmosphere worlds, with
|
||||
// the second pick preferring a different family for diversity. Tier order
|
||||
// in the rendered list is rolled too, to avoid positional bias.
|
||||
// Approval ratings weight the draw: a 3-star world earns a second ticket
|
||||
// (roughly double odds), a 1-star keeps its approval for direct briefs but
|
||||
// leaves the challenger pool unless a tier has nothing else.
|
||||
// Two separate axes, and both can exclude. Rating grades quality: a 3-star
|
||||
// earns a second ticket, a 1-star marginal keep leaves the pool. Breadth says
|
||||
// whether a world can serve an arbitrary build at all, so a niche world
|
||||
// leaves the pool however good it is. Breadth was split out of rating because
|
||||
// the only way to hold a narrow world back used to be calling it marginal,
|
||||
// which made "excellent but narrow" unrecordable and corrupted the ratings as
|
||||
// a calibration signal for the next authoring round.
|
||||
const ticketsFor = pool => pool.flatMap(concept => {
|
||||
const rating = concept.review?.rating;
|
||||
if (rating === 1) return [];
|
||||
// Two independent exclusions: a marginal world is too weak to challenge,
|
||||
// a niche world too narrow. Either one keeps its approval for direct
|
||||
// briefs and leaves the pool.
|
||||
if (rating === 1 || concept.review?.breadth === 'niche') return [];
|
||||
return rating === 3
|
||||
? [{ concept, ticket: 0 }, { concept, ticket: 1 }]
|
||||
: [{ concept, ticket: 0 }];
|
||||
|
||||
@@ -325,6 +325,61 @@ describe('concept seed scopes', () => {
|
||||
assert.equal(picks.some(pick => pick.id === 'lone-marginal'), true);
|
||||
});
|
||||
|
||||
it('holds niche worlds out of the challenger pool however strong their rating', () => {
|
||||
const make = (id, rating, breadth) => ({
|
||||
id,
|
||||
familyId: `${id}-family`,
|
||||
wellId: `${id}-well`,
|
||||
wellTier: 'graphic',
|
||||
strength: 'world',
|
||||
status: 'approved',
|
||||
form: `${id} form`,
|
||||
spark: `${id} spark`,
|
||||
system: [],
|
||||
webLeverage: `${id} web`,
|
||||
review: { status: 'approved', ...(rating ? { rating } : {}), ...(breadth ? { breadth } : {}) },
|
||||
});
|
||||
const filler = (tier, id) => ({ ...make(id), wellTier: tier });
|
||||
const pool = [
|
||||
make('broad-flagship', 3),
|
||||
make('narrow-flagship', 3, 'niche'),
|
||||
make('broad-solid', 2),
|
||||
filler('interaction', 'radar3'),
|
||||
filler('atmosphere', 'cavern3'),
|
||||
];
|
||||
// Breadth excludes independently of rating: over many keys a niche 3-star
|
||||
// never challenges while its broad peers keep rotating.
|
||||
for (let index = 0; index < 200; index += 1) {
|
||||
const { picks } = selectApprovedChallengers({ scope: 'direction', key: `breadth-${index}`, sourceConcepts: pool });
|
||||
assert.equal(picks.some(pick => pick.id === 'narrow-flagship'), false, `niche world drawn at key breadth-${index}`);
|
||||
}
|
||||
// A tier holding only niche approvals falls back rather than starving,
|
||||
// exactly like the marginal-only tier above.
|
||||
const onlyNiche = [
|
||||
make('lone-niche', 3, 'niche'),
|
||||
filler('interaction', 'radar4'),
|
||||
filler('atmosphere', 'cavern4'),
|
||||
];
|
||||
const { picks } = selectApprovedChallengers({ scope: 'direction', key: 'lone-niche', sourceConcepts: onlyNiche });
|
||||
assert.equal(picks.some(pick => pick.id === 'lone-niche'), true);
|
||||
});
|
||||
|
||||
it('gates stagings by breadth and falls back when every staging is niche', () => {
|
||||
const pool = [
|
||||
{ id: 'broad-stage', surface: 'persuade', status: 'approved' },
|
||||
{ id: 'niche-stage', surface: 'persuade', status: 'approved', review: { breadth: 'niche' } },
|
||||
];
|
||||
for (let index = 0; index < 60; index += 1) {
|
||||
const picks = selectApprovedStagings({ scope: 'direction', key: `stage-breadth-${index}`, mode: 'persuade', sourceCompositions: pool });
|
||||
assert.equal(picks.some(pick => pick.id === 'niche-stage'), false, `niche staging dealt at key stage-breadth-${index}`);
|
||||
}
|
||||
const allNiche = [
|
||||
{ id: 'only-niche-stage', surface: 'persuade', status: 'approved', review: { breadth: 'niche' } },
|
||||
];
|
||||
const fallback = selectApprovedStagings({ scope: 'direction', key: 'all-niche', mode: 'persuade', sourceCompositions: allNiche });
|
||||
assert.equal(fallback.some(pick => pick.id === 'only-niche-stage'), true, 'an all-niche pool must fall back instead of dealing nothing');
|
||||
});
|
||||
|
||||
it('mode-filters the fixture staging pool per surface register', () => {
|
||||
const operate = selectApprovedStaging({ scope: 'surface', key: 'fix-mode', mode: 'operate', sourceCompositions: fixtureCompositions });
|
||||
assert.equal(operate.surface, 'operate');
|
||||
|
||||
Reference in New Issue
Block a user