Skip to content

feat: surface the Fischer increment on TurnContext - #8

Merged
rabestro merged 5 commits into
mainfrom
feat/turncontext-increment
Jul 22, 2026
Merged

feat: surface the Fischer increment on TurnContext#8
rabestro merged 5 commits into
mainfrom
feat/turncontext-increment

Conversation

@rabestro

Copy link
Copy Markdown
Owner

What

WebhookHandler now parses state.timeControl from the webhook envelope and surfaces the per-turn Fischer increment as a new TurnContext.incrementMillis field (Long, milliseconds). It is null for any control without an increment (unlimited, sudden-death, per-move) or when the field is absent — the parsing is defensive, so a surprising shape never turns a valid turn into a 400.

Why

play-api already sends the full timeControl on every webhook turn (PublicGameState.timeControl), but the library dropped it — so a bot doing Fischer time management had to configure the increment out of band (e.g. dicechess-bot-gcp's LADDER_INCREMENT_MS env var). Now the increment rides the wire and every webhook bot gets correct Fischer budgeting for free, on whatever control the server pairs.

Wire shape

timeControl is a single-key object (Circe's derived enum codec); only Fischer carries an increment:

  • {"Fischer":{"initialSeconds":300,"incrementSeconds":3}} -> incrementMillis = 3000
  • {"Unlimited":{}}, {"SuddenDeath":{...}}, {"PerMove":{...}}, or absent -> null

Tests

Three new WebhookHandlerTest cases (Fischer -> ms, non-Fischer -> null, absent -> null). mvn test green: 32 tests, 0 failures.

Follow-ups (not in this PR)

  • Cut a 0.2.0 release (new public API) so consumers can depend on it.
  • dicechess-bot-gcp then drops its LADDER_INCREMENT_MS workaround and reads ctx.incrementMillis().

Closes #7

WebhookHandler now parses state.timeControl (present on every webhook turn) and exposes the per-turn Fischer increment as TurnContext.incrementMillis (Long ms), null for controls without one (unlimited/sudden-death/per-move) or an absent field. Defensive parsing never turns a valid turn into a 400. Bots doing Fischer time management no longer configure the increment out of band.

Closes #7

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@rabestro

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@github-actions

Copy link
Copy Markdown
Contributor

Qodana for JVM

It seems all right 👌

No new problems were found according to the checks applied

View the detailed Qodana report

To be able to view the detailed Qodana report, you can either:

To get *.log files or any other Qodana artifacts, run the action with upload-result option set to true,
so that the action will upload the files as the job artifacts:

      - name: 'Qodana Scan'
        uses: JetBrains/qodana-action@v2026.1.3
        with:
          upload-result: true
Contact Qodana team

Contact us at qodana-support@jetbrains.com

@rabestro rabestro self-assigned this Jul 22, 2026
@rabestro

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@rabestro, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 48 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3f93412a-be34-49fb-bb1c-e2af3143f2e9

📥 Commits

Reviewing files that changed from the base of the PR and between 70ab84a and c195188.

📒 Files selected for processing (1)
  • src/main/java/lv/id/jc/dicechess/runtime/WebhookHandler.java
📝 Walkthrough

Walkthrough

WebhookHandler parses Fischer increment data from webhook state, converts it to milliseconds, and exposes it through TurnContext.Clock. Documentation and tests cover Fischer, non-Fischer, absent, untimed, and malformed time-control inputs.

Changes

Turn time control context

Layer / File(s) Summary
TurnContext increment contract
src/main/java/lv/id/jc/dicechess/runtime/TurnContext.java, README.md
TurnContext now carries timing through Clock, including nullable Fischer incrementMillis; documentation describes null clock semantics for untimed games.
Webhook increment extraction and validation
src/main/java/lv/id/jc/dicechess/runtime/WebhookHandler.java, src/test/java/lv/id/jc/dicechess/runtime/WebhookHandlerTest.java
WebhookHandler validates and converts Fischer incrementSeconds, while tests cover Fischer, non-Fischer, absent, untimed, and malformed inputs.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant WebhookPayload
  participant WebhookHandler
  participant TurnContext
  participant Strategy
  WebhookPayload->>WebhookHandler: Provide state.timeControl.Fischer.incrementSeconds
  WebhookHandler->>WebhookHandler: Validate and convert seconds to milliseconds
  WebhookHandler->>TurnContext: Construct Clock with incrementMillis
  TurnContext->>Strategy: Expose Clock timing data
Loading
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The README badge additions are unrelated to exposing the Fischer increment and its docs update. Move the badge-only README edits to a separate cleanup PR and keep this PR focused on time-control exposure.
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: exposing Fischer increment on TurnContext.
Description check ✅ Passed The description accurately describes the webhook parsing, null handling, and tests added here.
Linked Issues check ✅ Passed The PR implements #7 by parsing state.timeControl, exposing incrementMillis, and handling absent or malformed controls without throwing.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Comment thread src/main/java/lv/id/jc/dicechess/runtime/WebhookHandler.java Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/java/lv/id/jc/dicechess/runtime/WebhookHandler.java`:
- Around line 162-168: Update the incrementSeconds handling in WebhookHandler to
parse the value defensively, treating null, non-primitive, non-numeric, and
out-of-range inputs as absent while leaving incrementMillis null. If conversion
succeeds, scale seconds to milliseconds with overflow-safe arithmetic before
assigning the result.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c921157a-c1e7-43ea-9aaf-dcf9b9ff0f2e

📥 Commits

Reviewing files that changed from the base of the PR and between 83aaa93 and 02fc972.

📒 Files selected for processing (4)
  • README.md
  • src/main/java/lv/id/jc/dicechess/runtime/TurnContext.java
  • src/main/java/lv/id/jc/dicechess/runtime/WebhookHandler.java
  • src/test/java/lv/id/jc/dicechess/runtime/WebhookHandlerTest.java

Comment thread src/main/java/lv/id/jc/dicechess/runtime/WebhookHandler.java Outdated
…ment parse

Review feedback on #8:

- Group remainingMillis/opponentRemainingMillis/incrementMillis into a nested TurnContext.Clock (a null clock == an untimed game), so the clock is one cohesive value and an impossible state — one side's clock present without the other — is unrepresentable. Best done now, before 0.2.0 ships and consumers pin to the flat fields.

- Parse the Fischer increment defensively (numeric, non-negative, within int range): a null/non-numeric/out-of-range incrementSeconds now degrades to a null increment instead of turning an otherwise-valid turn into a 400.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@rabestro

Copy link
Copy Markdown
Owner Author

Addressed both review points in 0d653a8:

1. Defensive increment parse (CodeRabbit, WebhookHandler). Correct catch — getAsLong() on a null/non-numeric/out-of-range incrementSeconds would have thrown, and handle() turns that into a 400, contradicting the "never fail a valid turn on an optional field" intent. Extracted fischerIncrementMillis(...): it accepts only a numeric, non-negative value within int range (so no input can throw or overflow the ×1000), and otherwise leaves the increment null. New test aMalformedIncrementDegradesToNullNotA400 pins the behaviour (200, null increment).

2. Group the clock fields into a record (@rabestro). Done — remainingMillis/opponentRemainingMillis/incrementMillis now live in a nested TurnContext.Clock, and TurnContext is back to four cohesive components (gameId, dfen, clock, legalMoves). Chose to do it now, before 0.2.0 ships and consumers pin to the flat fields — afterwards it's a breaking redo. Bonus: a null clock == an untimed game (one null check answers "is this timed?"), and the two remaining times are primitive long inside a present clock, so "one side's clock without the other" is unrepresentable.

mvn test green: 33 tests.

The pre-merge Linked Issues warning (version bump + consumer updates) is intentional — those are tracked as the follow-ups in the PR description: cut a 0.2.0 release, then switch dicechess-bot-gcp to ctx.clock().incrementMillis() and drop its LADDER_INCREMENT_MS env workaround.

Jegors Cemisovs and others added 2 commits July 22, 2026 20:19
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CI, Code Quality (Qodana), Javadoc, latest release, Java 25, and MIT — the library-appropriate set (distinct from the bot repos' Play/Leaderboard badges).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/java/lv/id/jc/dicechess/runtime/WebhookHandler.java`:
- Around line 257-262: Update the increment conversion in the webhook handling
flow around incrementSeconds so getAsLong() is caught as a NumberFormatException
and returns null for malformed values. Preserve the existing numeric range check
and millisecond conversion for valid values.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 749da3d9-d1f1-4705-b997-644f3fa96ebd

📥 Commits

Reviewing files that changed from the base of the PR and between 02fc972 and 70ab84a.

📒 Files selected for processing (4)
  • README.md
  • src/main/java/lv/id/jc/dicechess/runtime/TurnContext.java
  • src/main/java/lv/id/jc/dicechess/runtime/WebhookHandler.java
  • src/test/java/lv/id/jc/dicechess/runtime/WebhookHandlerTest.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • README.md

Comment thread src/main/java/lv/id/jc/dicechess/runtime/WebhookHandler.java Outdated
CodeRabbit follow-up on #8: getAsLong() documents a NumberFormatException for untrusted values. The isNumber() guard already routes to the non-throwing longValue() path in current Gson, but wrapping the conversion keeps TurnContext.Clock's 'no input can throw' contract true against Gson's documented API rather than its internals — a malformed increment stays a null increment, never a 400.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@rabestro
rabestro merged commit 0316fbb into main Jul 22, 2026
7 checks passed
@rabestro
rabestro deleted the feat/turncontext-increment branch July 22, 2026 18:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Surface the game's time control (Fischer increment) on TurnContext

1 participant