diff --git a/docs/countdown-engine-soa/CONTRIBUTING_TEMPLATE.md b/docs/countdown-engine-soa/CONTRIBUTING_TEMPLATE.md new file mode 100644 index 0000000..124e1e5 --- /dev/null +++ b/docs/countdown-engine-soa/CONTRIBUTING_TEMPLATE.md @@ -0,0 +1,22 @@ +# Contributing + +Thank you for contributing to the Countdown Engine. + +## Before submitting changes + +- add or update tests +- keep platform code outside the core engine +- preserve deterministic behavior +- document new commands and events +- version snapshot schema changes +- avoid official television branding and copyrighted media + +## Pull requests + +A pull request should include: + +- problem statement +- design summary +- tests +- compatibility impact +- migration notes when schemas change diff --git a/docs/countdown-engine-soa/DATA_MODELS.md b/docs/countdown-engine-soa/DATA_MODELS.md new file mode 100644 index 0000000..4e34201 --- /dev/null +++ b/docs/countdown-engine-soa/DATA_MODELS.md @@ -0,0 +1,36 @@ +# Core Data Models + +```cpp +struct MatchRules { + std::string scoringPolicyId; + uint32_t thinkingDurationMs; + bool allowHouseRules; +}; + +struct RoundResult { + RoundId roundId; + std::string roundType; + std::optional winnerId; + std::map scoreAwards; + bool tie; +}; + +struct MatchSnapshot { + uint32_t schemaVersion; + MatchState match; + ByteBuffer currentRoundSnapshot; + uint32_t checksum; +}; + +struct CommandError { + std::string code; + std::string message; + bool retryable; +}; + +struct CommandResult { + bool accepted; + std::vector events; + std::optional error; +}; +``` diff --git a/docs/countdown-engine-soa/DECISIONS.md b/docs/countdown-engine-soa/DECISIONS.md new file mode 100644 index 0000000..7ba836a --- /dev/null +++ b/docs/countdown-engine-soa/DECISIONS.md @@ -0,0 +1,33 @@ +# Architecture Decisions + +## ADR-001: Platform-neutral core + +The engine contains no display, touch, network, or storage implementation. + +## ADR-002: Command-event model + +Clients submit commands. The engine validates them and emits events. + +## ADR-003: Round plug-ins + +Numbers, Letters, and Conundrum are independent modules behind one shared interface. + +## ADR-004: Host-authoritative multiplayer + +Only the current host commits authoritative state changes. + +## ADR-005: Leader-based host ownership + +The score leader becomes host between rounds. Ties retain the current host. + +## ADR-006: Injected time and randomness + +Clock and random sources are interfaces to support deterministic tests. + +## ADR-007: Event replay + +Accepted events can reproduce match state and support debugging, recovery, and audit. + +## ADR-008: Separate content licensing + +Conundrum word-and-hint packs may use a license separate from the engine. diff --git a/docs/countdown-engine-soa/LICENSE_RECOMMENDATION.md b/docs/countdown-engine-soa/LICENSE_RECOMMENDATION.md new file mode 100644 index 0000000..f70abbc --- /dev/null +++ b/docs/countdown-engine-soa/LICENSE_RECOMMENDATION.md @@ -0,0 +1,15 @@ +# License Recommendation + +Recommended engine license: + +**Apache License 2.0** + +Why: + +- permissive use +- explicit patent grant +- suitable for libraries +- suitable for embedded and commercial integrations +- widely understood by open-source contributors + +Content packs, dictionaries, graphics, music, and television-derived assets must be reviewed and licensed independently. diff --git a/docs/countdown-engine-soa/PROTOCOL.md b/docs/countdown-engine-soa/PROTOCOL.md new file mode 100644 index 0000000..3b4b10a --- /dev/null +++ b/docs/countdown-engine-soa/PROTOCOL.md @@ -0,0 +1,62 @@ +# Multiplayer Protocol + +## Authority model + +One peer is authoritative at any time. + +The authoritative peer: + +- validates commands +- emits events +- assigns sequence numbers +- publishes snapshots +- commits scores +- manages host transfer + +## Message families + +```text +HELLO +CAPABILITIES +SYNC_REQUEST +SYNC_SNAPSHOT +COMMAND +EVENT +HOST_TRANSFER_PREPARE +HOST_TRANSFER_ACK +HOST_TRANSFER_COMMIT +PING +PONG +ERROR +``` + +## Required envelope fields + +```text +protocolVersion +engineVersion +matchId +roundId +senderPlayerId +hostPlayerId +hostTerm +sequenceNumber +messageType +payload +checksum +``` + +## Duplicate protection + +Commands should include a client-generated command ID. + +The host stores recently processed command IDs and returns the original result when a duplicate arrives. + +## Recovery + +A peer requests a snapshot when: + +- it reconnects +- it detects a sequence gap +- it receives a newer host term +- its local checksum differs diff --git a/docs/countdown-engine-soa/README.md b/docs/countdown-engine-soa/README.md new file mode 100644 index 0000000..79e4d71 --- /dev/null +++ b/docs/countdown-engine-soa/README.md @@ -0,0 +1,7 @@ +# Open-Source Countdown Engine + +A platform-neutral, open-source game engine for building Countdown-style games across embedded devices, desktop applications, mobile apps, web clients, and automated test environments. + +This package defines the architecture, contracts, data models, plug-in system, synchronization model, and implementation roadmap for a reusable Countdown engine. + +The engine is intentionally separate from any specific user interface or hardware platform. diff --git a/docs/countdown-engine-soa/ROADMAP.md b/docs/countdown-engine-soa/ROADMAP.md new file mode 100644 index 0000000..4498fb9 --- /dev/null +++ b/docs/countdown-engine-soa/ROADMAP.md @@ -0,0 +1,39 @@ +# Roadmap + +## Phase 1 — Engine foundation + +- core types +- commands and events +- reducer pattern +- deterministic random and time +- serialization + +## Phase 2 — Round modules + +- Numbers +- Letters +- Conundrum + +## Phase 3 — Match and multiplayer + +- scoring +- chooser flow +- snapshots +- authoritative host +- leader-based host migration + +## Phase 4 — Reference clients + +- terminal client +- desktop debug client +- CYD client + +## Phase 5 — Open-source readiness + +- Apache-2.0 license +- contribution guide +- code of conduct +- CI matrix +- API docs +- sample content +- first stable release diff --git a/docs/countdown-engine-soa/ROUND_PLUGIN_API.md b/docs/countdown-engine-soa/ROUND_PLUGIN_API.md new file mode 100644 index 0000000..3554845 --- /dev/null +++ b/docs/countdown-engine-soa/ROUND_PLUGIN_API.md @@ -0,0 +1,59 @@ +# Round Plug-in API + +## Required interface + +Every round plug-in must provide: + +- unique type ID +- version +- initial-state factory +- command handler +- event reducer +- snapshot serializer +- result producer +- completion flag + +```cpp +struct RoundDescriptor { + std::string typeId; + std::string version; + std::string minimumEngineVersion; +}; + +class IRoundPlugin { +public: + virtual ~IRoundPlugin() = default; + + virtual RoundDescriptor descriptor() const = 0; + virtual RoundState create( + const RoundConfig& config, + IRandomSource& random + ) const = 0; + + virtual CommandResult handle( + const RoundState& state, + const RoundCommand& command, + const CommandContext& context + ) const = 0; + + virtual RoundState reduce( + const RoundState& state, + const RoundEvent& event + ) const = 0; + + virtual RoundResult result( + const RoundState& state + ) const = 0; +}; +``` + +## Plug-in rules + +A plug-in must not: + +- draw UI +- access hardware directly +- call the system clock directly +- access networking directly +- mutate match totals directly +- bypass command validation diff --git a/docs/countdown-engine-soa/SOA.md b/docs/countdown-engine-soa/SOA.md new file mode 100644 index 0000000..63b4faa --- /dev/null +++ b/docs/countdown-engine-soa/SOA.md @@ -0,0 +1,965 @@ +# System-Oriented Architecture: Open-Source Countdown Engine + +## 1. Purpose + +Create a reusable, platform-neutral engine for Countdown-style games. + +The engine must support: + +- Numbers rounds +- Letters rounds +- Conundrum rounds +- Scorekeeping +- Match flow +- Player claims +- Presentation and verification +- Pluggable round types +- Multiple front ends +- Local and networked multiplayer +- Deterministic testing +- Embedded hardware targets +- Desktop, mobile, and web applications + +The engine must not depend on: + +- a specific display +- a specific touchscreen +- ESP32 APIs +- Android APIs +- browser APIs +- a specific transport +- a specific storage backend + +--- + +## 2. Architectural goals + +1. Keep game rules deterministic and testable. +2. Separate game rules from rendering and input. +3. Separate match state from round-specific state. +4. Make every round a plug-in. +5. Support authoritative-host multiplayer. +6. Allow host migration between rounds. +7. Make snapshots serializable. +8. Make replays reproducible. +9. Support custom scoring and house rules. +10. Preserve compatibility across multiple clients. + +--- + +## 3. Top-level architecture + +```text +Countdown Engine +├── Core Match Engine +│ ├── Players +│ ├── Scores +│ ├── Round order +│ ├── Current chooser +│ ├── Host authority +│ ├── Match lifecycle +│ └── Round history +│ +├── Round Plug-in API +│ ├── Numbers +│ ├── Letters +│ ├── Conundrum +│ └── Future rounds +│ +├── Rules Layer +│ ├── Validation +│ ├── Scoring +│ ├── Legal moves +│ ├── Claims +│ └── Results +│ +├── Session Layer +│ ├── Local multiplayer +│ ├── Peer-to-peer +│ ├── Host-authoritative multiplayer +│ ├── Host migration +│ └── Snapshot recovery +│ +├── Platform Adapters +│ ├── Display/UI +│ ├── Input +│ ├── Clock +│ ├── Random source +│ ├── Storage +│ ├── Networking +│ └── Audio/animation +│ +└── Tooling + ├── Test harness + ├── Replay runner + ├── Content validator + ├── Simulation runner + └── Compatibility tests +``` + +--- + +## 4. Core design boundary + +The engine receives commands and produces events. + +It does not directly draw screens or read buttons. + +```text +Client Input + ↓ +Command + ↓ +Countdown Engine + ↓ +Validated State Change + ↓ +Event + ↓ +UI / Network / Storage Adapters +``` + +Example: + +```text +SelectLargeNumberCount(2) + ↓ +NumbersRound validates command + ↓ +NumberSelectionUpdated + ↓ +UI redraws selection +``` + +This command-event model enables: + +- deterministic tests +- network replication +- replay +- undo where supported +- audit logs +- headless simulation + +--- + +## 5. Core match engine + +The match engine owns state that persists across rounds. + +### Responsibilities + +- players +- total scores +- current round number +- current round plug-in +- previous round winner +- next-round chooser +- current host authority +- match rules +- completed round history +- match completion + +### Suggested model + +```cpp +using PlayerId = uint8_t; +using RoundId = uint32_t; +using MatchId = uint32_t; + +struct Player { + PlayerId id; + std::string name; + int score; +}; + +struct HostAuthority { + PlayerId hostPlayerId; + uint32_t hostTerm; + uint32_t sequenceNumber; +}; + +struct MatchState { + MatchId matchId; + std::vector players; + RoundId currentRoundId; + std::string currentRoundType; + PlayerId previousWinnerId; + PlayerId chooserId; + HostAuthority authority; + bool complete; +}; +``` + +--- + +## 6. Round plug-in contract + +Every round type implements the same public contract. + +```cpp +class IRound { +public: + virtual ~IRound() = default; + + virtual std::string typeId() const = 0; + virtual RoundSnapshot snapshot() const = 0; + + virtual CommandResult handle( + const RoundCommand& command, + const CommandContext& context + ) = 0; + + virtual bool isComplete() const = 0; + virtual RoundResult result() const = 0; +}; +``` + +A round plug-in must define: + +- state +- commands +- events +- legal transitions +- validation rules +- scoring rules +- completion rules +- serialization format +- compatibility version + +--- + +## 7. Shared round lifecycle + +Most rounds use: + +```text +SETUP + ↓ +THINKING + ↓ +CLAIM + ↓ +PRESENTATION + ↓ +VERIFICATION + ↓ +RESULT +``` + +Not every round must use every phase. + +Conundrum may use: + +```text +SETUP + ↓ +ACTIVE + ↓ +ATTEMPT + ├── WRONG → ACTIVE + └── CORRECT → RESULT +``` + +The engine should model phases as round-owned state rather than hard-coding one universal sequence. + +--- + +## 8. Numbers plug-in + +### Responsibilities + +- number-pool configuration +- large/small number selection +- target generation +- simultaneous thinking phase +- private final-value claims +- claim reveal +- presentation order +- arithmetic verification +- score calculation + +### Core rules + +Default classic-style configuration: + +- 6 source numbers +- 0–4 large numbers +- remaining values from the small-number pool +- target generated within configured range +- legal operations: addition, subtraction, multiplication, division +- integer-only division +- configurable positive-intermediate-result rule +- source and intermediate values may not be reused illegally + +### Suggested commands + +```text +ChooseLargeCount +DrawNumbers +StartThinking +SubmitNumberClaim +RevealClaims +SelectOperand +SelectOperator +BankIntermediate +UndoStep +EndPresentation +``` + +### Suggested events + +```text +LargeCountChosen +NumbersDrawn +TargetGenerated +ThinkingStarted +NumberClaimSubmitted +ClaimsRevealed +CalculationStepAccepted +CalculationStepRejected +PresentationCompleted +NumbersRoundCompleted +``` + +--- + +## 9. Letters plug-in + +### Responsibilities + +- vowel/consonant pools +- weighted letter draws +- selection limits +- simultaneous thinking phase +- private length claims +- word presentation +- letter-usage validation +- optional dictionary validation +- score calculation + +### Default constraints + +- exactly 9 letters +- 3–5 vowels +- 4–6 consonants +- disable choices that exceed maxima +- disable choices that would prevent satisfying remaining minima + +### Suggested commands + +```text +DrawVowel +DrawConsonant +StartThinking +SubmitLengthClaim +RevealClaims +SelectPresentedLetter +SubmitWord +AcceptWord +RejectWord +``` + +### Dictionary boundary + +Dictionary validation must be behind an interface: + +```cpp +class IDictionary { +public: + virtual ~IDictionary() = default; + virtual bool contains(std::string_view word) const = 0; +}; +``` + +Implementations may include: + +- manual confirmation +- embedded word list +- desktop dictionary +- remote dictionary service +- custom tournament dictionary + +--- + +## 10. Conundrum plug-in + +### Responsibilities + +- word-and-hint content bank +- random entry selection +- scrambled layout +- per-player local order +- manual reshuffle +- forced reshuffle after wrong attempt +- automatic validation after nine selections +- first-correct winner + +### Content model + +```cpp +struct ConundrumEntry { + std::string answer; // Exactly nine letters + std::string hint; +}; +``` + +Suggested portable format: + +```text +ANSWER|HINT +BLUEPRINT|A detailed design or plan +COUNTDOWN|A backward sequence before an event +``` + +### Suggested commands + +```text +StartConundrum +SelectLetter +ClearSelection +Reshuffle +SubmitAttempt +``` + +### Suggested events + +```text +ConundrumLoaded +LettersShuffled +LetterSelected +AttemptRejected +ForcedReshuffle +ConundrumSolved +``` + +--- + +## 11. Commands and events + +Commands express intent. + +Events describe accepted facts. + +### Command envelope + +```cpp +struct CommandEnvelope { + MatchId matchId; + RoundId roundId; + PlayerId playerId; + uint32_t hostTerm; + uint32_t expectedSequence; + std::string commandType; + ByteBuffer payload; +}; +``` + +### Event envelope + +```cpp +struct EventEnvelope { + MatchId matchId; + RoundId roundId; + uint32_t hostTerm; + uint32_t sequenceNumber; + std::string eventType; + ByteBuffer payload; +}; +``` + +### Requirements + +- commands are validated before state mutation +- accepted commands emit one or more events +- rejected commands return structured errors +- events are deterministic +- events can rebuild state +- sequence numbers are monotonic within a host term + +--- + +## 12. Multiplayer model + +The default multiplayer mode is host-authoritative. + +### Host responsibilities + +- validate commands +- generate shared random results +- advance phases +- commit scores +- publish events +- provide snapshots +- resolve round completion + +### Peer responsibilities + +- submit player commands +- apply authoritative events +- render local state +- preserve private local interaction where permitted +- request recovery snapshots + +--- + +## 13. Leader-based host migration + +The score leader owns the host role. + +### Rules + +- evaluate host ownership only after a round result is committed +- higher score becomes host +- on a tie, keep the current host +- never migrate during an active round +- old host remains authoritative until transfer commit +- new host receives the full committed snapshot + +### Transfer protocol + +```text +ROUND COMMITTED + ↓ +DETERMINE LEADER + ↓ +HOST_TRANSFER_PREPARE + ↓ +SNAPSHOT_SENT + ↓ +SNAPSHOT_ACKNOWLEDGED + ↓ +HOST_TRANSFER_COMMIT + ↓ +INCREMENT HOST TERM + ↓ +NEW HOST ACTIVE +``` + +### Split-brain prevention + +Every authoritative event includes: + +- host player ID +- host term +- sequence number +- match ID +- round ID + +Clients reject stale authority terms. + +--- + +## 14. Randomness + +All random behavior must use an injected random source. + +```cpp +class IRandomSource { +public: + virtual ~IRandomSource() = default; + virtual uint32_t next(uint32_t upperExclusive) = 0; +}; +``` + +Benefits: + +- deterministic tests +- reproducible replays +- seeded simulations +- platform-neutral behavior + +Random decisions include: + +- number draws +- target generation +- letter draws +- Conundrum entry selection +- Conundrum shuffle + +--- + +## 15. Time + +The engine must not call platform clocks directly. + +```cpp +class IClock { +public: + virtual ~IClock() = default; + virtual uint64_t monotonicMilliseconds() const = 0; +}; +``` + +The round engine stores deadlines and durations. + +Clients decide how to render: + +- analog clock +- animation +- digital countdown +- sound +- silent visual timer + +--- + +## 16. Storage + +Persistence is provided through an adapter. + +```cpp +class IMatchStore { +public: + virtual ~IMatchStore() = default; + + virtual bool saveSnapshot( + const MatchSnapshot& snapshot + ) = 0; + + virtual std::optional loadLatest( + MatchId matchId + ) = 0; +}; +``` + +Possible implementations: + +- flash +- SD card +- SQLite +- browser storage +- cloud database +- plain files + +--- + +## 17. Serialization + +Recommended properties: + +- versioned schema +- forward-compatible fields +- explicit enum values +- deterministic encoding +- checksum support +- compact binary option for embedded targets +- human-readable JSON option for tooling + +Suggested formats: + +- JSON for development and debugging +- MessagePack or CBOR for embedded/network use +- event-log text format for replay tools + +--- + +## 18. Replay and audit + +Every accepted state change should be reproducible from events. + +A replay file should include: + +```text +MatchStarted +PlayerAdded +RoundSelected +NumbersDrawn +ThinkingStarted +ClaimSubmitted +ClaimsRevealed +CalculationStepAccepted +RoundCompleted +ScoreUpdated +HostTransferred +``` + +Uses: + +- bug reproduction +- tournament review +- deterministic regression tests +- demo playback +- state recovery + +--- + +## 19. Scoring policy + +Scoring must be configurable and external to core round mechanics. + +```cpp +class IScoringPolicy { +public: + virtual ~IScoringPolicy() = default; + + virtual ScoreAward score( + const RoundResult& result, + const MatchRules& rules + ) const = 0; +}; +``` + +Support: + +- television-style scoring +- fixed points +- house rules +- practice mode +- no-score mode +- tournament rules + +--- + +## 20. Front-end adapters + +The engine should support multiple clients. + +Examples: + +```text +countdown-engine-core +├── cyd-client +├── desktop-client +├── android-client +├── web-client +├── terminal-client +└── simulation-client +``` + +Each client implements: + +- rendering +- user input +- timer presentation +- networking +- storage as needed + +The client must not duplicate game rules. + +--- + +## 21. Repository structure + +```text +countdown-engine/ +├── README.md +├── LICENSE +├── CONTRIBUTING.md +├── CODE_OF_CONDUCT.md +├── CMakeLists.txt +├── include/ +│ └── countdown/ +│ ├── core/ +│ ├── match/ +│ ├── rounds/ +│ ├── protocol/ +│ ├── serialization/ +│ └── adapters/ +├── src/ +│ ├── core/ +│ ├── match/ +│ ├── rounds/ +│ │ ├── numbers/ +│ │ ├── letters/ +│ │ └── conundrum/ +│ ├── protocol/ +│ └── serialization/ +├── content/ +│ └── conundrum/ +├── tests/ +│ ├── unit/ +│ ├── integration/ +│ ├── protocol/ +│ ├── replay/ +│ └── simulation/ +├── tools/ +│ ├── replay-runner/ +│ ├── content-validator/ +│ └── simulator/ +├── examples/ +│ ├── terminal/ +│ └── local-two-player/ +└── docs/ + ├── SOA.md + ├── ROUND_PLUGIN_API.md + ├── PROTOCOL.md + ├── DATA_MODELS.md + ├── TEST_STRATEGY.md + └── ROADMAP.md +``` + +--- + +## 22. Licensing recommendation + +Use a permissive license unless stronger copyleft is desired. + +Recommended default: + +- Apache License 2.0 + +Reasons: + +- permissive commercial and personal use +- explicit patent grant +- friendly to embedded, mobile, and web adopters +- suitable for a reusable engine library + +Content packs may use separate licenses from the engine. + +--- + +## 23. Compatibility policy + +Use semantic versioning. + +```text +MAJOR.MINOR.PATCH +``` + +- MAJOR: incompatible API or saved-state changes +- MINOR: backward-compatible features and new rounds +- PATCH: fixes without contract changes + +Every round plug-in should expose: + +- plug-in ID +- plug-in version +- minimum engine version +- snapshot schema version + +--- + +## 24. Test strategy + +### Unit tests + +- legal and illegal arithmetic +- number consumption +- letter limits +- claim ordering +- Conundrum attempt checking +- score calculation +- host selection +- tie behavior + +### Property tests + +- no source number reused illegally +- letter counts always remain valid +- shuffle preserves all letters +- event replay reaches identical state +- stale host terms never overwrite newer state + +### Integration tests + +- full Numbers round +- full Letters round +- full Conundrum round +- host migration +- disconnect and snapshot recovery +- save and resume + +### Simulation tests + +- thousands of automated matches +- randomized disconnects +- delayed and duplicated messages +- host transfer failure +- invalid client commands + +--- + +## 25. Initial implementation roadmap + +### Milestone 1: Core contracts + +- MatchState +- command envelope +- event envelope +- snapshots +- deterministic random source +- fake clock +- serialization + +### Milestone 2: Match engine + +- players +- scores +- round registration +- chooser +- round result handling +- host authority + +### Milestone 3: Numbers plug-in + +- full deterministic rules +- claims +- presentation verification +- scoring integration + +### Milestone 4: Letters plug-in + +- constrained draws +- claims +- presentation validation +- dictionary interface + +### Milestone 5: Conundrum plug-in + +- word-and-hint bank +- shuffle +- attempts +- forced reshuffle +- winner detection + +### Milestone 6: Network protocol + +- host-authoritative commands +- events +- snapshots +- reconnect recovery +- host migration + +### Milestone 7: Reference clients + +- terminal client +- local two-player client +- CYD adapter + +### Milestone 8: Open-source release + +- license +- contribution guide +- code of conduct +- API docs +- examples +- CI +- tagged release + +--- + +## 26. Non-goals for version 1 + +- online matchmaking +- cloud accounts +- voice recognition +- AI hints +- automatic dictionary downloads +- official television branding +- copyrighted music or artwork +- platform-specific UI inside the core library + +--- + +## 27. Definition of done for version 1 + +Version 1 is complete when: + +- the engine runs without a graphical interface +- all three rounds are playable through commands +- state can be rebuilt from events +- matches can be saved and resumed +- host migration works between rounds +- deterministic tests cover core rules +- a terminal reference client can play a complete match +- a CYD client can consume the same engine contracts diff --git a/docs/countdown-engine-soa/TEST_STRATEGY.md b/docs/countdown-engine-soa/TEST_STRATEGY.md new file mode 100644 index 0000000..2e15e10 --- /dev/null +++ b/docs/countdown-engine-soa/TEST_STRATEGY.md @@ -0,0 +1,26 @@ +# Test Strategy + +## Core principle + +All game rules must be testable without a display, network, or real clock. + +## Required test doubles + +- FakeClock +- SeededRandomSource +- InMemoryMatchStore +- LoopbackTransport +- ManualDictionary +- RecordingEventSink + +## Critical regression cases + +- stale host event rejected +- host transfer on lead change +- host retained on tie +- reconnect restores exact state +- replay reproduces final score +- invalid Number step does not consume operands +- Letters choice buttons never allow illegal final composition +- Conundrum wrong answer preserves solution and forces only local reshuffle +- duplicate command does not apply twice