Allow group departures with bribe commitments
Verify and publish checkpoint container / container (push) Successful in 10m51s

This commit is contained in:
Quintine committed 2026-10-29 00:46:59 +11:00
1 parent 3e0fa2a8a2
commit 0faa7923b0
13 files changed
+437 -37

No files matched your search

+8 -2
View File
@@ -12,6 +12,9 @@ export type CoalitionNexusView = {
revision: number;
/** Absent preserves the original, at-most-three alliance prototype. */
variant?: 'unlimited';
/** Until group admission can reconcile a live bribe commitment, only
* departure remains available in this Nexus. */
admissionBlocked?: boolean;
};
export type CoalitionNexusIntent =
@@ -29,7 +32,7 @@ export function coalitionNexusOptions(view: CoalitionNexusView): {
const seated = view.players.some(player => player.id === view.me);
const ownGroup = view.groups.find(group => group.members.includes(view.me));
const occupied = new Set(view.groups.flatMap(group => group.members));
const invitations = !seated || view.proposal ? [] : view.groups.flatMap(group =>
const invitations = !seated || view.proposal || view.admissionBlocked ? [] : view.groups.flatMap(group =>
(group.members.length < 2 || group.members.length >= (view.variant === 'unlimited' ? 6 : 3)) ? [] : view.players
.filter(player => !occupied.has(player.id) &&
(player.id === view.me || group.members.includes(view.me)))
@@ -40,7 +43,7 @@ export function coalitionNexusOptions(view: CoalitionNexusView): {
return {
invitations,
mayLeave: seated && !!ownGroup && !view.proposal,
mayAccept: mayDecline && !view.proposal!.approvals.includes(view.me),
mayAccept: mayDecline && !view.admissionBlocked && !view.proposal!.approvals.includes(view.me),
mayDecline,
};
}
@@ -66,6 +69,7 @@ export function CoalitionNexus({ view, disabled = false, onIntent }: {
{view.groups.length ? <ul>{view.groups.map(group =>
<li key={group.id}>{names(group.members)}</li>)}</ul> : <p>There are no alliances yet.</p>}
{proposal ? <div aria-live="polite">
{view.admissionBlocked && <p>Acceptance is unavailable while a bribe commitment remains unresolved. You may decline this invitation.</p>}
<p>{name(proposal.proposer)} proposes adding {name(proposal.entrant)} to {names(proposal.original.members)}.</p>
<p>Approved: {names(proposal.approvals)}. Waiting for: {names(
[...proposal.original.members, proposal.entrant].filter(id => !proposal.approvals.includes(id)),
@@ -81,6 +85,8 @@ export function CoalitionNexus({ view, disabled = false, onIntent }: {
proposal.approvals.includes(view.me) ? 'Withdraw approval and cancel invitation' :
'Decline and cancel invitation'}</button>}
</div> : <div>
{view.admissionBlocked && <p>Invitations are unavailable while a bribe commitment remains unresolved.
Finish the commitment or wait until its binding period ends.</p>}
{options.invitations.length > 0 && <p>{view.variant === 'unlimited'
? 'Invite an unallied player to an existing alliance, or ask to join one.'
: 'Invite an unallied player to an existing pair, or ask to join a pair.'}</p>}