Keep Truthtrance bid answers playable
This commit is contained in:
1 parent
2916b4d48f
commit
65a1936b94
6 files changed
+188
-17
No files matched your search
+33
-15
@@ -16095,14 +16095,18 @@ function phaseBidHonorsTurnActions(g: Game, p: Player, lot: BidOpportunity, acti
|
||||
/** Current witnesses must also satisfy the existing native funding and
|
||||
* current-lot commitments. Future bidding with other financial/transport
|
||||
* promises awaits their combined planner; do not invent those resources. */
|
||||
function phaseBidFuturePlannerUnsupported(g: Game, p: Player): boolean {
|
||||
return liveShipmentPromises(g.shipmentPromises ?? [],p.id,g.turn).some(row => row.answer) ||
|
||||
hasMovementPromises(g,p.id,true) || liveRevivalPromises(g.revivalPromises ?? [],p.id,g.turn).some(row => row.answer) ||
|
||||
liveLeaderRevivalPromises(g.leaderRevivalPromises ?? [],p.id,g.turn).some(row => row.answer) ||
|
||||
liveRevivalExpressionPromises(g.revivalExpressionPromises ?? [],p.id,g.turn).some(row => row.answer) ||
|
||||
liveTurnActionPromises(g.turnActionPromises ?? [],p.id,g.turn).length > 0 ||
|
||||
(!!bidOpportunity(g) && hasBribePromises(g,p.id,true));
|
||||
}
|
||||
function viablePhaseBidWitnesses(g: Game, p: Player, promises: readonly PhaseBidPromise[]) {
|
||||
const lot = bidOpportunity(g);
|
||||
const live = livePhaseBidPromises(promises,p.id,g.turn);
|
||||
const otherCommitments = liveShipmentPromises(g.shipmentPromises ?? [],p.id,g.turn).some(row => row.answer) ||
|
||||
hasMovementPromises(g,p.id) || liveRevivalPromises(g.revivalPromises ?? [],p.id,g.turn).some(row => row.answer) ||
|
||||
liveLeaderRevivalPromises(g.leaderRevivalPromises ?? [],p.id,g.turn).some(row => row.answer) ||
|
||||
liveRevivalExpressionPromises(g.revivalExpressionPromises ?? [],p.id,g.turn).some(row => row.answer) ||
|
||||
liveTurnActionPromises(g.turnActionPromises ?? [],p.id,g.turn).length > 0;
|
||||
const otherCommitments = phaseBidFuturePlannerUnsupported(g,p);
|
||||
return phaseBidWitnesses(g,p).flatMap(witness => {
|
||||
if (witness.kind === 'future') {
|
||||
if (otherCommitments) return [];
|
||||
@@ -16151,12 +16155,20 @@ function viablePhaseBidWitnesses(g: Game, p: Player, promises: readonly PhaseBid
|
||||
/** A new independent bribe must preserve an already-binding phase Yes.
|
||||
* Before Bidding, reserve currently fundable payment first; current-lot
|
||||
* independent combinations wait for a joint planner. */
|
||||
/** An independently admitted promise must not make an already-owned phase
|
||||
* bid impossible. If the future joint planner has no witness, a definite
|
||||
* answer is withheld rather than offered and rejected after binding. */
|
||||
function phaseBidCompatibleLaterPromises(g: Game, p: Player): boolean {
|
||||
const live = livePhaseBidPromises(g.phaseBidPromises ?? [],p.id,g.turn);
|
||||
if (!live.some(row => row.answer)) return true;
|
||||
return viablePhaseBidWitnesses(g,p,g.phaseBidPromises ?? [])
|
||||
.some(witness => !!phaseBidCompletionRange(g.phaseBidPromises ?? [],witness));
|
||||
}
|
||||
function phaseBidCompatibleBribeAnswer(g: Game, p: Player): boolean {
|
||||
const live = livePhaseBidPromises(g.phaseBidPromises ?? [],p.id,g.turn);
|
||||
if (!live.some(row => row.answer)) return true;
|
||||
if (g.phase === 3 && hasBribePromises(g,p.id,true)) return false;
|
||||
return viablePhaseBidWitnesses(g,p,g.phaseBidPromises ?? [])
|
||||
.some(witness => !!phaseBidCompletionRange(g.phaseBidPromises ?? [],witness));
|
||||
return phaseBidCompatibleLaterPromises(g,p);
|
||||
}
|
||||
function phaseBidRule<T>(run: () => T): T {
|
||||
try { return run(); }
|
||||
@@ -16208,7 +16220,8 @@ function reconcilePhaseBids(g: Game, voluntary?: {actor:string;action:Action}) {
|
||||
const witnesses = viablePhaseBidWitnesses(g,player,g.phaseBidPromises);
|
||||
// An external event must not release an answer merely because the
|
||||
// combined future-obligation planner is not implemented yet.
|
||||
const unknownFuture = change === 'external' && !witnesses.some(row => row.kind === 'future') &&
|
||||
const unknownFuture = change === 'external' && phaseBidFuturePlannerUnsupported(g,player) &&
|
||||
!witnesses.some(row => row.kind === 'future') &&
|
||||
phaseBidWitnesses(g,player).some(row => row.kind === 'future');
|
||||
if (unknownFuture && live.some(row => row.answer) &&
|
||||
!witnesses.some(row => !!phaseBidCompletionRange(g.phaseBidPromises!,row))) continue;
|
||||
@@ -20987,7 +21000,7 @@ function travelTruthAnswers(g: Game, p: Player, claim: TravelClaim): TruthAnswer
|
||||
// slot until the real answer binds. The authoritative order stays untouched.
|
||||
const trial = { ...answerSource, travelPromises: [...(g.travelPromises ?? []), promise], travelPromiseOrder: undefined };
|
||||
const lot = bidOpportunity(trial);
|
||||
if (travelCompletion(trial, p.id) && revivalCompatibleLaterPromises(trial, p, {}) &&
|
||||
if (phaseBidCompatibleLaterPromises(trial,p) && travelCompletion(trial, p.id) && revivalCompatibleLaterPromises(trial, p, {}) &&
|
||||
bribeCompatibleLaterPromises(trial, p, {}) &&
|
||||
(!lot || !bidRequired(trial.bidPromises ?? [], p.id, lot) || !!bidPromiseCompletion(trial, p)))
|
||||
answers.push(answer ? 'yes' : 'no');
|
||||
@@ -21342,7 +21355,8 @@ function movementTruthAnswers(g: Game, p: Player, claim: MovementClaim): TruthAn
|
||||
for (const answer of [true, false]) {
|
||||
const promise = { ...claim, turn: g.turn, player: p.id, asker: g.players.find(other => other.id !== p.id)!.id, answer };
|
||||
const answerSource = (g.phase === 2 && !g.charityOrder) || (g.phase === 4 && !g.revivalOrder) ? { ...g, ready: g.ready.filter(id => id !== p.id) } : g;
|
||||
if (groundMovementCompletion(answerSource, p.id, [...live, promise]) &&
|
||||
if (phaseBidCompatibleLaterPromises({ ...answerSource, movementPromises: [...(g.movementPromises ?? []), promise] },p) &&
|
||||
groundMovementCompletion(answerSource, p.id, [...live, promise]) &&
|
||||
revivalCompatibleLaterPromises(answerSource, p, { movementPromises: [...(g.movementPromises ?? []), promise] }) &&
|
||||
bribeCompatibleLaterPromises(answerSource, p, { movementPromises: [...(g.movementPromises ?? []), promise] }))
|
||||
answers.push(answer ? 'yes' : 'no');
|
||||
@@ -21371,7 +21385,7 @@ function movementExpressionTruthAnswers(g: Game, p: Player, claim: MovementExpre
|
||||
asker: g.players.find(other => other.id !== p.id)!.id, answer, progress: [] };
|
||||
const expressions = [...(g.movementExpressionPromises ?? []), promise];
|
||||
const trial = { ...answerSource, movementExpressionPromises: expressions };
|
||||
if (groundMovementCompletion(trial, p.id) &&
|
||||
if (phaseBidCompatibleLaterPromises(trial,p) && groundMovementCompletion(trial, p.id) &&
|
||||
revivalCompatibleLaterPromises(answerSource, p, { movementExpressionPromises: expressions }) &&
|
||||
bribeCompatibleLaterPromises(answerSource, p, { movementExpressionPromises: expressions })) answers.push(answer ? 'yes' : 'no');
|
||||
}
|
||||
@@ -30117,7 +30131,8 @@ function shipmentTruthAnswers(
|
||||
// Concurrent legacy readiness can reopen; a completed ordered turn cannot.
|
||||
const answerSource = (g.phase === 2 && !g.charityOrder) || (g.phase === 4 && !g.revivalOrder)
|
||||
? { ...g, ready: g.ready.filter(id => id !== p.id) } : g;
|
||||
if (findShipmentCompletion(answerSource, p, promises) &&
|
||||
if (phaseBidCompatibleLaterPromises({ ...answerSource, shipmentPromises: [...(g.shipmentPromises ?? []), promises[promises.length - 1]] },p) &&
|
||||
findShipmentCompletion(answerSource, p, promises) &&
|
||||
revivalCompatibleLaterPromises(answerSource, p, { shipmentPromises: [...(g.shipmentPromises ?? []), promises[promises.length - 1]] }) &&
|
||||
bribeCompatibleLaterPromises(answerSource, p, { shipmentPromises: [...(g.shipmentPromises ?? []), promises[promises.length - 1]] }))
|
||||
answers.push(answer ? 'yes' : 'no');
|
||||
@@ -33808,7 +33823,8 @@ function revivalTruthAnswers(g: Game, p: Player, claim: RevivalClaim): ('yes' |
|
||||
return (['yes', 'no'] as const).filter(answer => {
|
||||
const promises = [...(g.revivalPromises ?? []), { ...claim, event: 'candidate', player: p.id,
|
||||
asker: g.truthtrance?.queue[0]?.player ?? g.host, answer: answer === 'yes', progress: 0 }];
|
||||
if (!revivalPromiseCompletion(g, p, promises) ||
|
||||
if (!phaseBidCompatibleLaterPromises({ ...g, revivalPromises: promises },p) ||
|
||||
!revivalPromiseCompletion(g, p, promises) ||
|
||||
!bribeCompatibleLaterPromises(g, p, { revivalPromises: promises })) return false;
|
||||
const lot = bidOpportunity(g);
|
||||
// An earlier Bid Yes reserves a future declaration even before it becomes
|
||||
@@ -33847,7 +33863,8 @@ function leaderRevivalTruthAnswers(g: Game, p: Player, claim: LeaderRevivalClaim
|
||||
const promises = [...(g.leaderRevivalPromises ?? []), { ...claim, event: 'candidate', player: p.id,
|
||||
asker: g.truthtrance?.queue[0]?.player ?? g.host, answer: answer === 'yes' }];
|
||||
const trial = { ...g, leaderRevivalPromises: promises };
|
||||
if (!revivalPromiseCompletion(trial, p) || !bribeCompatibleLaterPromises(trial, p, {})) return false;
|
||||
if (!phaseBidCompatibleLaterPromises(trial,p) || !revivalPromiseCompletion(trial, p) ||
|
||||
!bribeCompatibleLaterPromises(trial, p, {})) return false;
|
||||
const lot = bidOpportunity(trial);
|
||||
return !lot || !bidRequired(trial.bidPromises ?? [], p.id, lot) || !!bidPromiseCompletion(trial, p);
|
||||
});
|
||||
@@ -33871,7 +33888,8 @@ function revivalExpressionTruthAnswers(g: Game, p: Player, claim: RevivalExpress
|
||||
asker: g.truthtrance?.queue[0]?.player ?? g.host, answer: answer === 'yes',
|
||||
progress: { total: 0, normal: 0, elite: 0, leaders: [] } }];
|
||||
const trial = { ...g, revivalExpressionPromises: promises };
|
||||
if (!revivalPromiseCompletion(trial, p) || !bribeCompatibleLaterPromises(trial, p, {})) return false;
|
||||
if (!phaseBidCompatibleLaterPromises(trial,p) || !revivalPromiseCompletion(trial, p) ||
|
||||
!bribeCompatibleLaterPromises(trial, p, {})) return false;
|
||||
const lot = bidOpportunity(trial);
|
||||
return !lot || !bidRequired(trial.bidPromises ?? [], p.id, lot) || !!bidPromiseCompletion(trial, p);
|
||||
});
|
||||
|
||||
+2
-2
@@ -4434,11 +4434,11 @@ export const RULE_TOPICS: RuleTopic[] = [
|
||||
category: 'Advanced & expansions',
|
||||
coverage: 'Partial',
|
||||
developmentStage: 'Integrated',
|
||||
summary: 'Allied Ecaz and Fremen may share Sietch Tabr while qualifying for the Fremen special victory.',
|
||||
summary: 'Allied Ecaz and Fremen may share Sietch Tabr, and in Advanced Habbanya Sietch, while qualifying for the Fremen special victory.',
|
||||
steps: [
|
||||
'At the final-turn victory check, ordinary stronghold victories come first. If none qualifies, Fremen’s special occupation conditions are checked before the Guild fallback. A qualifying Fremen victory includes its ally and is not replaced by a Bene Gesserit prediction.',
|
||||
'Ecaz and Fremen must be current reciprocal allies and both have fighters in Sietch Tabr. Their co-occupation does not block the Fremen special condition. Ecaz alone, non-allied Ecaz or another faction’s fighters still block it. Accompanying Bene Gesserit advisors do not count as fighters.',
|
||||
'Habbanya Sietch retains its existing empty-or-Fremen condition; extending the Ecaz exception there is not yet supported. Tuek’s Sietch must contain no Atreides, Harkonnen or Emperor presence, and Advanced play also excludes Richese presence. A concealed No-Field counts without revealing its hidden value.',
|
||||
'In Basic, Habbanya Sietch must be empty or occupied only by Fremen. In Advanced, reciprocal allied Ecaz and Fremen may also share Habbanya Sietch when both have fighters there. Tuek’s Sietch must contain no Atreides, Harkonnen or Emperor presence, and Advanced play also excludes Richese presence. A concealed No-Field counts without revealing its hidden value.',
|
||||
'The public victory guide lists the three territories and any blocking factions. It uses the same settled-board facts as automatic victory resolution. No extra confirmation is required; the table chronicle explains a successful special victory.',
|
||||
],
|
||||
checklist: [
|
||||
|
||||
Reference in new issue
Block a user