Update channels
- ci.yml now publishes each green push to dev as a prerelease tagged
v1.0.<n>-dev, so the dev channel has something to install. Workflow
artifacts could not serve this: downloading one needs authentication,
which the app does not have.
- Prereleases specifically, because GitHub's "latest release" endpoint
skips them — a production install can never be handed a dev build.
Pull requests build and check but never publish.
- Settings gains a channel picker. The two channels are numbered by
separate workflow run counters, so versions are only compared within a
channel; the app reads the -dev suffix in its own versionName to know
where it stands, and treats a channel change as an explicit switch. The
screen warns that such a switch can be a downgrade Android will refuse.
- PLG_RELEASE becomes PLG_CHANNEL, naming the channel rather than a flag.
Euchre
- Cards in a trick now sit on the side of the table belonging to whoever
played them, rotated to face them, labelled You / Left / Partner /
Right. Previously they were a flat row tagged with seat numbers, which
made it hard to tell who had played what.
- Bidding says what it will actually do. As dealer the button reads "Pick
it up" rather than "Order up", since the turn card comes to your hand,
and a line of explanatory text spells out the consequence either way.
Sequence
- Playable squares get a much heavier outline, scaled off cell size and
in a fixed amber that reads against a white card, a green corner and a
chip alike. The old 2dp theme-coloured border was hard to see.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015qjfMiPUDshdKEUa6vxqzN
Two ways the updater could have served or blocked the wrong build:
- release.yml also triggers on workflow_dispatch, which can be aimed at
any branch from the Actions UI. That would have built dev code and
published it as a real Release, which every install would then offer to
download. The job now refuses to run off anything but main.
- versionCode came from GITHUB_RUN_NUMBER, but that counter is per
workflow, so the dev pipeline had its own independent sequence. A debug
APK installed from a CI artifact already carried a higher versionCode
than the first several releases would, and the updater's check is
latest <= installed, so those users would have been told they were up
to date indefinitely. Only release.yml now sets PLG_RELEASE, and only
that stamps a real version; everything else stays at 1.0.0-dev.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015qjfMiPUDshdKEUa6vxqzN
Lint flagged ACCESS_FINE_LOCATION declared without ACCESS_COARSE_LOCATION,
which Android 12 requires to be requested together.
Rather than add the missing one, drop location entirely: the Bluetooth
transport only talks to already-paired devices, enumerating bonded
devices and opening an RFCOMM socket. Neither is a scan, so no location
access is needed. BLUETOOTH_SCAN and BLUETOOTH_ADVERTISE go for the same
reason — nothing was using them.
Discovery of unpaired devices would bring all of these back; the manifest
notes what that would require.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015qjfMiPUDshdKEUa6vxqzN
- Transport.kt used the reified Json.encodeToString extension without
importing kotlinx.serialization.encodeToString, so it resolved to the
two-argument member overload and failed to compile.
- Opt in to ExperimentalMaterial3Api for the clickable game tile.
- Drop --stacktrace from the CI build. A one-line Kotlin error was being
buried under 150 lines of Gradle internals, which made the actual
message hard to find in the log.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015qjfMiPUDshdKEUa6vxqzN
First vertical slice: the whole shell plus two games playable end to end,
so every layer is exercised before the other nine games are built on it.
Games
- Euchre: bowers, ordering up, dealer discard, going alone, stick the
dealer, and full scoring. Rule variants (deck size, points to win,
stick the dealer, going alone) are toggles in game setup.
- Sequence: two-eyed and one-eyed jacks, free corners, locked sequences,
the shared-chip limit, and dead-card exchange. Teams, players per team
and the sequence target are toggles.
- The board layout in SequenceBoard is generated, not the commercial
board — it holds every structural property the rules need, but does not
match a physical board. Documented in docs/RULES-sequence.md.
Multiplayer
- One transport abstraction over newline-delimited JSON, implemented
twice: LAN via NSD discovery and TCP, Bluetooth via Classic RFCOMM.
A lobby advertises on both at once, so a table can mix them.
- Host-authoritative: clients send move intents, the host re-validates
every one against the rules, and each client receives a state redacted
for its own seat so hidden information never crosses the wire.
- Seats start as AI and are handed to people as they join, which is what
makes any mix of humans and AI work without a separate code path.
Shell
- Main menu lists all eleven games; the nine without engines are marked
coming soon and are driven entirely by GameCatalog.
- Settings: sound, animation speed (one multiplier drives card movement
and AI thinking time), and OTA updates from the latest GitHub release.
- Layouts measure available space rather than assuming an orientation.
The game package is free of Android types, so the rules engines and AI
run under plain JUnit; 43 tests cover ranking, following suit, scoring,
sequence detection, redaction, wire round-trips and full random and AI
playthroughs.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015qjfMiPUDshdKEUa6vxqzN