Maven Artifact & JVM Integration
The engine is the single source of truth for Dice Chess rules across the ecosystem. JVM backends — first of all dicechess-analytics (the Scala 3 analytics backend) — consume it as a regular Maven dependency instead of re-implementing game logic.
Non-Scala JVM callers (Java, Kotlin) bind to a dedicated facade rather than to the Scala API directly — see Consuming from Java or Kotlin below.
Every release publishes two JVM coordinates alongside the NPM packages, at one shared version:
dicechess-rules_3 (from 0.11.0) | dicechess-engine_3 | |
|---|---|---|
| Group ID | com.fortemate | com.fortemate |
| Contents | rules only: domain model, DFEN, move generation, legal turns, dice probabilities | bots, evaluators, feature extractors, JvmApi; depends on dicechess-rules_3 |
| Canonical registry | Maven Central | Maven Central |
| Authenticated mirror | GitHub Packages | GitHub Packages |
Which coordinate a project needs, and how a rules-only consumer switches, is covered in
Published Artifacts & Rules-Only Migration. The rest of
this page uses the engine coordinate in its examples; a rules-only project substitutes
dicechess-rules / dicechess-rules_3.
Consuming the Artifact (sbt)
Section titled “Consuming the Artifact (sbt)”Maven Central is the default resolver for all JVM build tools — no extra resolver or authentication is needed.
libraryDependencies += "com.fortemate" %% "dicechess-engine" % "<latest release>"// rules only (0.11.0+): "com.fortemate" %% "dicechess-rules" % "<latest release>"ONNX Consumers Declare onnxruntime Directly
Section titled “ONNX Consumers Declare onnxruntime Directly”The published dicechess-engine_3 POM marks com.microsoft.onnxruntime:onnxruntime as an optional dependency (<optional>true</optional>). This prevents downstream rules-only consumers (such as dicechess-play-api or analytics services) from dragging in the ~54 MB ONNX native binaries when they do not evaluate ML models.
Consumers that use ONNX-backed search bots (OnnxEvalSearch, OnnxExpectimaxSearch) must declare onnxruntime directly in their own build configuration and match the version used by their engine release (the 0.13.0 release uses 1.30.0):
libraryDependencies ++= Seq( "com.fortemate" %% "dicechess-engine" % "<latest release>", "com.microsoft.onnxruntime" % "onnxruntime" % "1.30.0")<dependencies> <dependency> <groupId>com.fortemate</groupId> <artifactId>dicechess-engine_3</artifactId> <version>${dicechess.engine.version}</version> </dependency> <dependency> <groupId>com.microsoft.onnxruntime</groupId> <artifactId>onnxruntime</artifactId> <version>1.30.0</version> </dependency></dependencies>Consuming from Java or Kotlin
Section titled “Consuming from Java or Kotlin”Java and Kotlin callers depend on the same artifact, but bind to
dicechess.engine.jvmapi.JvmApi rather than to the
Scala API. The Scala surface leans on constructs that do not survive the language boundary
intact — Either returns, extension methods (which compile onto synthetic $package classes with
no ordinary entry point), and opaque types like Move that erase to int, turning a
List[List[Move]] into an unchecked List[List[Object]] of boxed integers. JvmApi keeps all of
that away from the caller: its signatures use java.util types, primitives, opaque handles passed
straight back to the engine, and one facade-owned result type, JvmApi.Turn.
Java callability is pinned by a Java-source test that CI compiles and runs on every build; Kotlin
consumes the same static methods and java.util types but is not exercised here — see
What “Java and Kotlin” is based on.
Note the _3 suffix in the artifact ID: Maven has no equivalent of sbt’s %% operator, so the
Scala binary-version suffix has to be spelled out.
<properties> <dicechess.engine.version><!-- latest release --></dicechess.engine.version></properties>
<dependencies> <dependency> <groupId>com.fortemate</groupId> <artifactId>dicechess-engine_3</artifactId> <version>${dicechess.engine.version}</version> </dependency></dependencies>No <repositories> block is needed — Maven Central is configured by default in all standard
Maven installations.
dicechess-bot-java is the reference consumer.
The Scala runtime is two artifacts, not one
Section titled “The Scala runtime is two artifacts, not one”The engine’s POM declares org.scala-lang:scala3-library_3, but since the Scala 3.8 library
unification that artifact is an empty shim — its jar contains a manifest and nothing else. The
actual runtime classes live in org.scala-lang:scala-library of the same version, which the shim
pulls in as a transitive dependency.
Maven, Gradle, and sbt resolve that transitively, so a normal dependency declaration needs no extra
work. It breaks when something keeps scala-library off the runtime classpath: a hand-assembled
classpath (a hand-listed -cp, a shaded or “fat” jar built from a hand-picked file list, a container
image that copies selected jars), or dependency metadata that excludes it, marks it optional, or
narrows it to provided scope. Such a classpath links and starts fine and then fails on the first
real call into the engine:
java.lang.NoClassDefFoundError: scala/util/boundary$BreakThe fix is to put scala-library on the classpath as well (or let the build tool compute the
classpath). Verifying is one command — the shim is the jar with a single entry:
unzip -l scala3-library_3-*.jarGitHub Packages mirror
Section titled “GitHub Packages mirror”Maven Central is the recommended resolver because it is public and requires no configuration. Every
new release is also mirrored to GitHub Packages under the same coordinates for authenticated GitHub
consumers. The mirror requires a GitHub token with read:packages:
resolvers += "GitHub Packages (dicechess-engine)" at "https://maven.pkg.github.com/fortemate/dicechess-engine"credentials += Credentials( "GitHub Package Registry", "maven.pkg.github.com", sys.env("GITHUB_ACTOR"), sys.env("GITHUB_TOKEN"))
libraryDependencies += "com.fortemate" %% "dicechess-engine" % "<latest release>"Use the mirror only when GitHub-authenticated dependency resolution is intentional. Do not commit a
token to the build; inject GITHUB_ACTOR and GITHUB_TOKEN from the consumer’s secret store.
Local Development Against Unreleased Changes
Section titled “Local Development Against Unreleased Changes”When a downstream project needs engine changes that are not released yet, publish the JVM artifact to the local Ivy repository:
mise run publish:localThis publishes the current -SNAPSHOT version to ~/.ivy2/local, where sbt resolves it
before any remote registry.
Maven does not read ~/.ivy2/local, so a Maven-built consumer needs the artifact in the local
Maven repository instead:
sbt rootJVM/publishM2That writes to ~/.m2/repository, where Maven picks it up. Point the consumer’s
dicechess.engine.version at the -SNAPSHOT value while iterating, and remember to move it back
to a real release before opening a PR — CI has no access to your local repository, so a
-SNAPSHOT dependency that builds locally fails there.
How Publishing Works
Section titled “How Publishing Works”build.sbtsetssonatypeCredentialHost := "central.sonatype.com", selecting the new Sonatype Central Portal.sbt-ci-release(viaproject/plugins.sbt) manages the canonicalpublishToautomatically: a release version goes to Sonatype staging, a-SNAPSHOTgoes to the snapshot repository.- The Maven Central copy is GPG-signed by
sbt-pgpusing the org key stored in thePGP_SECRETorg secret; the passphrase is inPGP_PASSPHRASE. Maven Central verifies the signature against the public key published atkeyserver.ubuntu.com. - Both CD workflows (
release.yamlandpublish.yaml) first run.mise/lib/maven-registry-state.sh, which checks the POM plus main, sources, and javadoc jars of each coordinate (dicechess-rules_3,dicechess-engine_3) on the authenticated GitHub Packages registry. A coordinate is published only when its complete version is absent; a partial immutable version fails closed. The same per-coordinate check runs against Maven Central; the absent coordinates are uploaded withrulesJVM/publishSignedand/orrootJVM/publishSignedand promoted together with onesonaRelease(one Central Portal deployment named after both coordinates). - Every publish is preceded by the guards of both rows: no coverage instrumentation, no bench
classes,
META-INF/LICENSEin every jar, a rules POM without third-party compile dependencies, andonnxruntimemarked optional in the engine POM. - Publish commands run with
sbt --server, which executes a foreground process instead of the persistent native thin client. This prevents credentials or coverage settings from a previous session leaking into publication and avoids thin-client startup deadlocks. - The
benchmark,arena, andclimodules are excluded viapublish / skip := true. - The steps are intentionally duplicated in both workflows: tags pushed by
release.yamlviaGITHUB_TOKENdo not triggerpublish.yaml(GitHub’s recursion guard).
See CI/CD & Automated Releases for the full pipeline. full pipeline.