Package com.fortemate.dicechess.runtime
A DiceChess bot is a small HTTP endpoint: the platform (play-api) delivers each turn or
optional decision as a signed HTTP request, and the bot answers with a typed action. Everything
in this package is the plumbing around that contract — HMAC-SHA256 signature verification (Signatures), the one-time ownership handshake and delivery
orchestration (WebhookHandler), and (optionally) the HTTP
server itself (CustomHandlerServer) — so a bot author
supplies one BotStrategy.
The whole wiring for a bot's main method:
BotStrategy strategy = context -> new TurnAction(List.of("e2e4"));
String secret = System.getenv("DICECHESS_WEBHOOK_SECRET");
WebhookHandler handler = new WebhookHandler(secret, strategy);
CustomHandlerServer.startFromEnvironment(handler);
Only JDK types cross the public boundary — no Gson type, and nothing library-specific, appears
in a public signature. BotStrategy is a functional
interface: BotStrategy.onTurn(TurnContext) is its single
abstract method, while BotStrategy.onDrawDecision(DrawDecisionContext) safely declines
by default. Java, Kotlin, and Scala consumers can therefore continue to use a lambda when they
need only normal turns.
Draw decisions
The exact lowercase draws webhook capability opts an endpoint into pre-roll
drawDecision deliveries. Without that capability, play-api declines the offer on the bot's
behalf, reveals the dice, and sends a normal yourTurn. With it, the strategy receives a
dice-free DrawDecisionContext and returns a DrawAction. The default callback returns DrawAction.decline(), so enabling runtime v2 never silently
accepts a draw.
BotStrategy strategy = new BotStrategy() {
@Override
public TurnAction onTurn(TurnContext context) {
return new TurnAction(List.of("e2e4")); // offerDraw defaults to false
}
@Override
public DrawAction onDrawDecision(DrawDecisionContext context) {
return DrawAction.decline(); // accept only after an evaluated bot policy says so
}
};
Stake doubling
The exact lowercase doubling webhook capability opts an endpoint into pre-roll stake
doubling decisions in staked games. Staked games use closed-loop PLAY_CREDIT units and
follow the accepted play-api contract (ADR-0019). The platform resolves any pending draw
first. If no draw is pending and the turn owner is eligible to offer, play-api delivers a dice-free
doubleOpportunity before rolling. If an offer is made, the responder receives a dice-free
doubleDecision before the roll point.
A bot author implements an intentional policy by overriding BotStrategy.onDoubleOpportunity(DoubleOpportunityContext) and/or
BotStrategy.onDoubleDecision(DoubleDecisionContext). Both
methods have safe defaults:
onDoubleOpportunitydefaults toDoubleOfferAction.roll(), proceeding to roll without doubling;onDoubleDecisiondefaults toDoubleResponseAction.decline(), declining an incoming offer.
BotStrategy strategy = new BotStrategy() {
@Override
public TurnAction onTurn(TurnContext context) {
return new TurnAction(List.of("e2e4"));
}
@Override
public DoubleOfferAction onDoubleOpportunity(DoubleOpportunityContext context) {
// Evaluate engine policy using context.currentStake(), context.cubeValue(), context.dfen()
return DoubleOfferAction.roll();
}
@Override
public DoubleResponseAction onDoubleDecision(DoubleDecisionContext context) {
// Evaluate engine take/drop threshold using context.proposedStake(), context.offeredBy()
return DoubleResponseAction.decline();
}
};
Bots with no engine of their own
TurnContext.legalMoves carries every complete legal
turn, already walked from the server's prefix tree — a strategy can pick straight from that
list and never parse a DFEN or generate a move itself. An empty list means the server is
auto-passing, so no bot action is required; null means the tree is unknown. The rare turn
where the tree is too large to inline falls back to GET
/games/{id}/moves — a public, unauthenticated endpoint — if the WebhookHandler constructor that takes play-api's base URL was
used; otherwise legalMoves is simply null on that turn, same as it always is
when a strategy doesn't need it.
TurnContext.clock is null for an untimed game.
Otherwise the GameClock values are milliseconds from the
bot's point of view; only a Fischer control has a non-null increment. TurnContext.mayOfferDraw fails closed to false when the
optional wire field is absent, null, or malformed.
Key rotation and verification v2 (ADR 004)
To support staged secret management, URL replacement, and zero-downtime same-URL secret rotation,
WebhookKeys provides an immutable key-set configuration:
- Active only: steady-state delivery verification;
- Pending only: initial registration of an unverified endpoint before activation;
- Active and pending: staged rotation during which activation challenges are verified using the pending key only, while ongoing gameplay deliveries are accepted under either key.
When both keys are configured, delivery signature verification evaluates both keys using constant-time comparisons without early return to eliminate timing oracles, and never exposes which key matched.
Legacy version-1 and version-absent verification challenges continue to echo the nonce
without signature validation. Challenges declaring "version": 2 require signed headers over the exact
raw request body with the pending key, and return an independent cryptographic HMAC response proof.
Single-key endpoints cannot perform safe same-URL session rotation without downtime.
Concurrency
One strategy instance is shared by its handler. CustomHandlerServer uses virtual threads, so callbacks for
different games may overlap. Contexts and actions are immutable snapshots; mutable engine,
cache, or per-game state captured by a strategy must be synchronized or otherwise thread-safe.
Migration from v1
Version 2 intentionally removes the v1 Function<TurnContext, List<String>> callback
and redesigns TurnContext. There is no compatibility
constructor or callback adapter in the v2 artifact: return a TurnAction from onTurn, update context access, and
override onDrawDecision only when adopting the draws capability. Previously
published immutable v1 artifacts remain unchanged.
What is deliberately not here
DFEN parsing and independent move legality are still not this package's concern — it
relays the server's own tree rather than recomputing one, so an engine-linked bot is free to
ignore legalMoves entirely and keep deriving moves from dfen itself. It also does
not read or write an opening book itself; JsonFiles is a generic string-map loader a strategy can use for
that, or for any similarly simple lookup table.
-
ClassDescriptionDecision-oriented strategy contract for a DiceChess webhook bot.A minimal HTTP server for the Azure Functions custom-handler model: one path, one
WebhookHandler, no framework.Authenticated pre-roll context for deciding an opponent's pending stake double offer.A bot's response to adoubleOpportunitydelivery.Authenticated pre-roll context for deciding whether to offer a stake double on this bot's turn.A bot's response to adoubleDecisiondelivery.Common pre-roll context for stake-doubling decisions in a staked game.An authoritative stake-doubling decision awaiting bot action.An offer decision awaiting the turn owner's choice to offer a double or roll.A response decision awaiting the responder's choice to accept or decline an offer.The doubling state of a staked game.A bot's response to adrawDecisiondelivery.Authenticated pre-roll context for deciding an opponent's pending draw offer.The game clock visible to a bot decision.Loads a JSON object of string keys to string values from a file — the shape an opening book, or any similarly simple lookup table, is exported as.Policy governing whether a bot should resign incoming gameplay deliveries.An HTTP response for a webhook delivery: a status code and a JSON body, ready to write to whatever HTTP layer the caller is using.HMAC-SHA256 signing and verification for the DiceChess webhook delivery protocol.A bot's response to ayourTurndelivery.Authenticated context for the turn a bot must play.Authenticates and dispatches DiceChess webhook deliveries to aBotStrategy.Immutable configuration for active and pending webhook secrets.