Extend Truthtrance facts and Basic Semuta Tech
Verify and publish checkpoint container / container (push) Failing after 10m53s
Verify and publish checkpoint container / container (push) Failing after 10m53s
This commit is contained in:
1 parent
16f10beecf
commit
7a083ac9aa
12 files changed
+361
-6
No files matched your search
+4
-1
@@ -13282,7 +13282,10 @@ function bareBasicSemutaProfile(g: Game): boolean {
|
||||
new Set(g.expansions).size === g.expansions.length &&
|
||||
g.players.every(p => faction(p.faction).expansion === 'base' ||
|
||||
g.expansions.includes(faction(p.faction).expansion)) &&
|
||||
!g.homeworlds && !g.nexusCards && !g.leaderSkills && !g.techTokens &&
|
||||
!g.homeworlds && !g.nexusCards && !g.leaderSkills &&
|
||||
(!g.techTokens || g.expansions.length === 1 && g.expansions[0] === 'choam' &&
|
||||
g.players.every(p => p.faction === 'richese' || CLASSIC_FACTIONS[p.faction] === true) &&
|
||||
!g.kullPreview && !g.nexusKullPreview && nativeFactionTechProfile(g, true)) &&
|
||||
!g.strongholdCards && !g.ecazTreachery && !g.discoveryEnabled &&
|
||||
!g.discoveries && !g.discoveryStash && !g.greatMaker;
|
||||
}
|
||||
|
||||
+2
-1
@@ -1341,6 +1341,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
? 'In the explicit Richese development preview, every seat sees a neutral response to supported clean discards. A CHOAM & Richese game may include the Ix deck: Thumper pauses before worm/spice draw; Amal pauses after halving spice before opening resumes. Other public sources include ordinary cards, paid Box, Ornithopter, Distrans, definite ordinary Truthtrance answers including queued questions, supported shipment promises and battle answers before or after plan submission/reveal, four clean Sapho scopes, pre-leader Residual Poison, printed Karama canceling clean CHOAM Charity, Inflation or Bene Gesserit Charity, a clean printed Karama normal-auction purchase or winning-bid payment before lot settlement, a completed CHOAM card sale after its bank income, clean Kulon Worthless disposal before its movement bonus, proactive or reactive La La La disposal before blocking free revival, proactive or reactive Baliset before restricting movement, proactive Trip to Gamont before its ordinary/elite return or native No-Field reveal and capped recall, accepted Jubba Cloak before storm protection, and three battle batches. A private normal-auction or Black Market Ixian ally replacement also pauses before drawing, without showing its face until a holder commits; original payment and seller income settle once. Cache purchases cannot use native Ixian allied replacement. Ordinary definite Truthtrance answers pause even when another question stays reserved or the answer bound a supported shipment or unsubmitted battle-plan promise. Submitted/revealed plans bind no new promise, but their answers still offer Semuta without changing the plan. Under the user-selected exchange ruling, Commit may replace Semuta even at your faction hand limit: acquisition and Semuta disposal settle atomically, without increasing your completed hand. Reserved incoming cards still need room. For multiple eligible cards, inspect and select exactly one after commitment. Saved effects resume once. Other private discards, Karama uses, other reactive Worthless uses, inspected Residual Poison battles, nested responses, other pending rewards and wider interactions remain unfinished.'
|
||||
: 'Reference component only: this Richese card’s game actions are not enabled.',
|
||||
...(definition.card.effect === 'semutaDrug' ? [
|
||||
'In fresh Basic games with Richese, classic opponents, the CHOAM deck and Tech Tokens, the existing Semuta preview is also enabled. Other module combinations remain separate; existing started games are not retrofitted.',
|
||||
'Fresh Advanced Richese preview games with only the CHOAM expansion deck and classic or CHOAM opponents enable Semuta reactions for supported ordinary Hajr, Ghola, Harvester and Weather Control discards, completed Box and Distrans, simple definite Truthtrance answers, clean Residual Poison, completed CHOAM sales, supported printed CHOAM Worthless costs and supported battle cleanup. Other discard parents and optional-module combinations remain unfinished. Existing games are not retrofitted.',
|
||||
'Mandatory and winner battle discards also pause before native Advanced Harkonnen capture. Claim or Continue completes the original casualties and card cleanup before the capture offer; it does not sample a captive early or repeat capture. Other pending rewards remain outside this bounded path.',
|
||||
'Mandatory and winner battle discards also pause before native Tleilaxu Face Dance. Claim or Continue preserves the original winner’s casualties and card cleanup, then restores the same private Face Dancer opportunity. Replacement, leader death and force sources remain native; the claimed Treachery card stays with its claimant, and no Face Dancer card becomes a Semuta target.',
|
||||
@@ -3989,7 +3990,7 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
steps: [
|
||||
'Declare the card from your hand at any time after cards are dealt. Other players may declare a competing Truthtrance or pass; questions are then asked in storm order. The interrupted phase, response, or battle decision resumes afterward.',
|
||||
'Ask a game-related yes/no question. Named-card, selected-traitor, currently unrevealed Face Dancer and current personal-spice questions are checked by the server. Face Dancer and ordinary Traitor holdings stay distinct. AND and OR combine facts; only the combined answer is published, with no extra inventory details or separate answer for each clause.',
|
||||
'In a new freeform game-fact question, exact personal-spice, named-card and total-hand-size forms are checked as current facts. Two spice or card facts may be joined by one AND or OR; only their aggregate answer is published. The recognized interpretation appears before asking and with the question. Other wording and current-turn promises remain player-managed; bots answer unknown. Already-pending older questions keep their original behavior.',
|
||||
'In a new freeform game-fact question, exact personal-spice, named-card, total-hand-size and selected weapon, defense or Worthless category forms are checked as current facts. Category questions use the displayed printed primary role. Two spice or named-card facts may be joined by one AND or OR; only their aggregate answer is published. The recognized interpretation appears before asking and with the question. Other wording and current-turn promises remain player-managed; bots answer unknown. Already-pending older questions keep their original behavior.',
|
||||
'For current spice, choose Exactly, At least or At most and a nonnegative whole amount. This counts spice personally held when answering, excluding separately pledged aid and incoming payments. It does not ask whether a particular action is affordable or promise that the balance will stay unchanged. A true current-spice answer does not prevent later legal spending.',
|
||||
'For a concealed No-Field, choose its current printed value0,3 or5, not the prospective force pool. A missing or already revealed marker answers No, including for0; a concealed5 with empty reserves still answers Yes to5. Only the respondent sees the expected aggregate before public reply. Neither question nor answer turns up the physical token or commits later use; current-force facts continue to exclude markers.',
|
||||
'The questioned player answers publicly. A definite answer discards the card. If the answer cannot be known, the holder may ask a different question or save Truthtrance for later.',
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { parseFreeformFactV2, type FreeformFactV2 } from './truthtrance-freeform-facts-v2';
|
||||
import type { HandInventoryFact } from './truthtrance-hand-inventory';
|
||||
|
||||
export type FreeformFactV3 = FreeformFactV2 | HandInventoryFact;
|
||||
|
||||
/** Exact category phrases only. These mean the existing printed primary role,
|
||||
* not every card a faction might use in that slot through a special power. */
|
||||
const categories: Record<string, HandInventoryFact['category']> = {
|
||||
weapon: 'weapon', weapons: 'weapon',
|
||||
defense: 'defense', defenses: 'defense',
|
||||
'worthless card': 'worthless', 'worthless cards': 'worthless',
|
||||
'projectile weapon': 'projectileWeapon', 'projectile weapons': 'projectileWeapon',
|
||||
'poison weapon': 'poisonWeapon', 'poison weapons': 'poisonWeapon',
|
||||
'projectile defense': 'projectileDefense', 'projectile defenses': 'projectileDefense',
|
||||
'poison defense': 'poisonDefense', 'poison defenses': 'poisonDefense',
|
||||
};
|
||||
|
||||
/**
|
||||
* Version 3 extends the stable v2 grammar with anchored, current hand-category
|
||||
* questions. Saved v1 and v2 receipts must continue using their own parsers.
|
||||
* The caller restricts this to fact-scoped questions and displays the primary
|
||||
* role interpretation before an answer is checked.
|
||||
*/
|
||||
export function parseFreeformFactV3(text: string): FreeformFactV3 | null {
|
||||
if (typeof text !== 'string' || text.length > 500) return null;
|
||||
const existing = parseFreeformFactV2(text);
|
||||
if (existing) return existing;
|
||||
const input = text.trim();
|
||||
const match = /^do[ \t]+you[ \t]+have[ \t]+(?:(a|an|any)|(at[ \t]+least|exactly|at[ \t]+most)[ \t]+([0-9]+))[ \t]+([a-z]+(?:[ \t]+[a-z]+)*?)(?:[ \t]+in[ \t]+your[ \t]+hand)?[ \t]*\??$/i.exec(input);
|
||||
if (!match) return null;
|
||||
const label = match[4].toLowerCase().replace(/[ \t]+/g, ' ');
|
||||
if (!Object.hasOwn(categories, label)) return null;
|
||||
const category = categories[label];
|
||||
if (match[1] && /^(?:a|an)$/i.test(match[1]) && label.endsWith('s')) return null;
|
||||
const count = match[3] === undefined ? 1 : Number(match[3]);
|
||||
if (!Number.isSafeInteger(count) || count < 0) return null;
|
||||
const comparison = match[2]?.toLowerCase().replace(/[ \t]+/g, ' ');
|
||||
return { kind: 'handInventory', category,
|
||||
compare: comparison === 'exactly' ? 'eq' : comparison === 'at most' ? 'lte' : 'gte',
|
||||
value: count };
|
||||
}
|
||||
+10
-2
@@ -66,6 +66,7 @@ import { coalitionAllies } from './coalition-alliance';
|
||||
import { TRUTH_CARD_NAMES } from './truthtrance-card-names';
|
||||
import { parseFreeformFactV1 } from './truthtrance-freeform-facts';
|
||||
import { parseFreeformFactV2 } from './truthtrance-freeform-facts-v2';
|
||||
import { parseFreeformFactV3 } from './truthtrance-freeform-facts-v3';
|
||||
import { CHEAP_HERO_TRAITOR } from './traitors';
|
||||
import {
|
||||
TRUTH_FACT_MAX_NODES, TRUTH_FACT_MAX_DEPTH, TRUTH_FACT_MAX_TERMS,
|
||||
@@ -129,7 +130,7 @@ export type TruthQuestion =
|
||||
text: string;
|
||||
scope: 'fact' | 'currentTurn';
|
||||
/** Only newly asked, explicitly recognized facts carry this stable interpretation. */
|
||||
interpreted?: { version: 1; fact: Extract<TruthFact, { kind: 'hand' | 'spice' }> } | { version: 2; fact: TruthFact };
|
||||
interpreted?: { version: 1; fact: Extract<TruthFact, { kind: 'hand' | 'spice' }> } | { version: 2 | 3; fact: TruthFact };
|
||||
};
|
||||
export type TruthAnswer = 'yes' | 'no' | 'unknown';
|
||||
export type TruthQueueEntry = {
|
||||
@@ -657,8 +658,11 @@ function parseQuestion(g: Game, asker: string, value: unknown): TruthQuestion {
|
||||
const marker = record(v.interpreted);
|
||||
const oldFact = question.scope === 'fact' && marker.version === 1 ? parseFreeformFactV1(question.text) : null;
|
||||
const newFact = question.scope === 'fact' && marker.version === 2 ? parseFreeformFactV2(question.text) : null;
|
||||
const categoryFact = question.scope === 'fact' && marker.version === 3 && !parseFreeformFactV2(question.text)
|
||||
? parseFreeformFactV3(question.text) : null;
|
||||
const expected = oldFact ? { version: 1 as const, fact: oldFact }
|
||||
: newFact ? { version: 2 as const, fact: newFact } : null;
|
||||
: newFact ? { version: 2 as const, fact: newFact }
|
||||
: categoryFact ? { version: 3 as const, fact: categoryFact } : null;
|
||||
check(expected !== null && JSON.stringify(v.interpreted) === JSON.stringify(expected),
|
||||
'The saved freeform interpretation does not match its original question.');
|
||||
question.interpreted = expected;
|
||||
@@ -1119,6 +1123,10 @@ export function resolveTruthAction(
|
||||
if (w.question.kind === 'freeform' && w.question.scope === 'fact') {
|
||||
const fact = parseFreeformFactV2(w.question.text);
|
||||
if (fact) w.question.interpreted = { version: 2, fact };
|
||||
else {
|
||||
const categoryFact = parseFreeformFactV3(w.question.text);
|
||||
if (categoryFact) w.question.interpreted = { version: 3, fact: categoryFact };
|
||||
}
|
||||
}
|
||||
if (w.question.kind === 'mixedBattlePlan')
|
||||
check(effects.mixedBattleAnswers?.(g, g.players.find(other => other.id === w.question!.target)!, w.question).length,
|
||||
|
||||
Reference in new issue
Block a user