diff --git a/docs/CURRENT_STATUS.md b/docs/CURRENT_STATUS.md index 9ee4c967..df22ecf8 100644 --- a/docs/CURRENT_STATUS.md +++ b/docs/CURRENT_STATUS.md @@ -20,6 +20,13 @@ open mode gates. Keep identified Basic-function gaps ahead of Advanced and wider interactions. Prototype labels and final acceptance gates remain. +**Silent automatic submission notice — 27 October.** +A full hand's forced Silent submission now uses the same public text as a +manual sealed bid and a neutral animation tag, instead of announcing the +reason for a forced zero. Public hand counts can still allow inference. The +existing native reveal and automatic settlement remain unchanged. Five focused +files pass 54/54; type/lint passed before the intervening test-only CI fix. + **Richese Silent Truthtrance bid — 27 October. Prototyped / Partial.** The event-bound Richese question also supports one sealed Silent submission. Yes requires a qualifying bid; No permits zero or a lower bid. Feasibility uses diff --git a/docs/IMPLEMENTATION_STATUS.md b/docs/IMPLEMENTATION_STATUS.md index df3e0d98..253fa930 100644 --- a/docs/IMPLEMENTATION_STATUS.md +++ b/docs/IMPLEMENTATION_STATUS.md @@ -1,5 +1,16 @@ # Dune implementation status +## 27 October 2026 — Silent automatic notice + +Use the same public text for forced and manual sealed submissions, and a +neutral automatic tag, so the notice itself does not identify a full hand or a +zero bid. Public hand counts can still permit inference. Basic/Advanced cases +compare real manual and forced events while retaining sealed views, promise +receipts and conserved physical cards; the full-hand setup is controlled. +Five focused files pass 54/54; type/lint passed before the intervening test-only +CI fix. Private reports are `20261027-silent-neutral-notice-proof-v1/` and +`20261027-silent-neutral-notice-proof-v2/`. No browser or deployment result is claimed. + ## 27 October 2026 — Richese Silent bid Truthtrance Extend the current public-event bid question to a single sealed Silent diff --git a/docs/TRUTHTRANCE_BID_PROMISES.md b/docs/TRUTHTRANCE_BID_PROMISES.md index 71d47120..8f80a12a 100644 --- a/docs/TRUTHTRANCE_BID_PROMISES.md +++ b/docs/TRUTHTRANCE_BID_PROMISES.md @@ -9,7 +9,8 @@ The answer and guide use only the respondent's actual available funds and aid, not other sealed amounts. All eligible submissions retain native escrow and reveal simultaneously in original tie order; only the winner pays. When a hand becomes full, the native automatic zero releases an impossible Yes or completes -No. Its public text and action tag do not announce the amount before reveal. +No. Its public text matches a manual sealed submission and its action tag is neutral. Public hand counts can still allow inference of a forced zero. Five focused +files pass 54/54; type/lint passed before the intervening test-only CI fix. The [GF9 November 2020 FAQ p8](https://www.gf9games.com/dune/wp-content/uploads/2020/11/Dune-FAQ-Nov-2020.pdf#page=8) supports action commitments while possible, but does not supply this specific diff --git a/game/engine.ts b/game/engine.ts index 674955cb..f60ed892 100644 --- a/game/engine.ts +++ b/game/engine.ts @@ -18581,8 +18581,10 @@ function finishAutomaticDecision(g: Game): boolean { const player = getPlayer(g, full); log( g, - `${player.name} automatically ${lot.method === 'silent' ? 'submitted a sealed bid' : 'passed'} because their hand became full before bidding.`, - { faction: player.faction, name: lot.method === 'silent' ? 'Full hand: sealed submission' : 'Full hand: no bid' }, + lot.method === 'silent' + ? `${player.name} submitted a sealed bid; its amount remains concealed until every eligible bidder submits.` + : `${player.name} automatically passed because their hand became full before bidding.`, + { faction: player.faction, name: lot.method === 'silent' ? 'Sealed bid' : 'Full hand: no bid' }, ); if (lot.method === 'silent' && g.richeseAuction.outcome) log( diff --git a/tests/richese-engine.test.ts b/tests/richese-engine.test.ts index 5c9c4287..e5b9f300 100644 --- a/tests/richese-engine.test.ts +++ b/tests/richese-engine.test.ts @@ -325,7 +325,7 @@ void test('restored full hands automatically submit zero or remove an unbid cach assert.equal(g.players[0].spice, 22); assert.equal(g.players[0].hand.length, 4); assert.ok( - g.log.some((entry) => entry.text.includes('automatically submitted a sealed bid')), + g.log.some((entry) => entry.text.includes('submitted a sealed bid; its amount remains concealed')), ); let unbid = cache(); for (const id of ['h', 'e', 'r']) unbid = bid(unbid, id, 0); diff --git a/tests/truth-richese-silent-runtime.test.ts b/tests/truth-richese-silent-runtime.test.ts index e6aecb06..558bf02d 100644 --- a/tests/truth-richese-silent-runtime.test.ts +++ b/tests/truth-richese-silent-runtime.test.ts @@ -113,6 +113,15 @@ for (const advanced of [false, true]) for (const answer of ['yes','no'] as const } assert.deepEqual(inventory(harmed),f.originalCards); assert.equal(Object.hasOwn(harmed.richeseAuction!.sealed,f.target),false); + // Compare the automatic notice to an actual manual submission from the + // same public game state. The original Yes can bid 4; No can submit zero. + const manualBefore = bound.log.at(-1)?.seq ?? 0; + const manual = act(bound,f.target,{type:'richeseBid',event:f.lot, + amount:answer === 'yes' ? 4 : 0,allyPayment:0}); + const manualNotice = manual.log.find(entry => entry.seq > manualBefore && + entry.text.includes('submitted a sealed bid')); assert.ok(manualNotice); + assert.equal(viewGame(manual,f.owner).richeseAuction?.revealedBids,null); + assert.deepEqual(inventory(manual),f.originalCards); const beforeSequence = harmed.log.at(-1)?.seq ?? 0; const settled = normalizeAutomaticGame(harmed); const receipt = settled.richeseBidPromises!.find(row => row.player === f.target); assert.ok(receipt); @@ -123,9 +132,13 @@ for (const advanced of [false, true]) for (const answer of ['yes','no'] as const assert.equal(observer.ownBid,null); assert.equal(observer.revealedBids,null); assert.ok(observer.submitted.includes(f.target)); const fresh = settled.log.filter(entry => entry.seq > beforeSequence); - assert.ok(fresh.some(entry => entry.text.includes('submitted a sealed bid'))); - assert.ok(fresh.every(entry => !/revealed together|zero|0 spice|no bid/i.test(`${entry.text} ${entry.automatic?.name ?? ''}`)), - 'Automatic public notices must not disclose the sealed zero before reveal'); + const automaticNotice = fresh.find(entry => entry.text.includes('submitted a sealed bid')); assert.ok(automaticNotice); + assert.equal(automaticNotice.text,manualNotice.text, + 'Forced and manual submissions use the same public notice before reveal'); + assert.ok(!automaticNotice.automatic || automaticNotice.automatic.name === 'Sealed bid', + 'An optional automatic-action tag must use a neutral name'); + assert.ok(fresh.every(entry => !/revealed together|zero|0 spice|no bid|full hand|hand became full/i.test(`${entry.text} ${entry.automatic?.name ?? ''}`)), + 'Automatic public notices and tags must not directly disclose the forced-zero reason before reveal'); assert.deepEqual(inventory(settled),f.originalCards); }); }