What this port is made of and the decisions that are not obvious from the source.
For how to build and run it see development-setup.md; for the conventions the code follows see CONTRIBUTING.md; for the present release posture see STATUS.md.
- Origins and authority
- Module layout
- The launcher
- The native data boundary
- The generated definitions
- The vendored runtime
- Game data
- Where it stands
ChonkCraft is self-contained source and native Java at build and runtime. Its GPL ancestry and formal attribution are documented in the README; those ancestor names are intentionally kept out of implementation identifiers and configuration.
The behavioral authority is Warcraft II: Battle.net Edition 2.02b. Retail archives supply player-owned data, the pinned Windows executable supplies observable behavior, and authenticated captures connect findings to focused Java tests. Historical GPL code remains provenance and supporting evidence, but is not an external checkout, build input, runtime dependency, or competing behavior profile.
Seven Maven modules, in reactor order.
| Module | What it is |
|---|---|
assetpack |
The pack container format and the codecs it needs, including a pure-Java Opus encoder and decoder |
runtime |
Vendored desktop foundation -- timing, Java2D, fullscreen, PCM mixer. See the rule for changing it |
data |
Archive readers, sprite and tileset decoders, PUD maps, and the only code that knows what a 1995 installation directory looks like |
extractor |
Builds an asset pack from an installation. Isolated from the engine by a test that fails on a reference in the wrong direction |
launcher |
The durable first screen: source-media import, graphics-pack selection, game updates and child-process launch |
engine |
The simulation: units, orders, combat, pathfinding, fog, AI, spells, triggers, save and load |
desktop |
The replaceable game entry point and its screens, panels, input and sound policy |
Two walls are enforced by tests rather than remembered:
engineanddesktophave no concept of an installation directory.NoInstallDirectoryTestin each module reads its own sources and fails on a reference toWarcraft2Install,WarArchive,CdAudioorCdImage. They read anAssetSource; locating a 1995 install isdata's job alone.- The extractor cannot see the engine.
IsolationTestis the same wall from the other side.
The shipped runtime is deliberately two-file: the game JAR supplies behavior, typed catalogs, mission declarations and presentation policy; the authenticated BNE chonkpack supplies the player's retail maps, media and decoded archive data. The former retired scripting language parser/interpreter module and script-content distribution were deleted after field-complete differentials and player/referee gates. See native-data-boundary.md for authority and provenance.
The engine is retail Battle.net Edition 2.02b and nothing else. There is no
legacy compatibility profile, no flag to consult, and no second path to keep alive; the
field that used to say so, World.battleNetProfile, was a private final boolean set to true, which made it a JLS constant variable that javac
folded at compile time. Every obsolete compatibility branch behind it had been compiled out of
the shipped game for as long as the flag had been true, and the source now
says what the bytecode already meant.
World remains the simulation's state owner and its cycle orchestrator. It
holds the unit table, the map, the players, both random streams, the missile
list and the per-cycle bookkeeping, and tick still drives the cycle. What it
no longer holds is the rules. Those live in eight package-private
collaborators beside it, each constructed once by World and each holding the
World it reads:
| Collaborator | The question it answers |
|---|---|
BattleNetIdleSystem |
What a unit does when it has been given nothing to do -- BNE's startup Still dispatcher and its animation markers |
BattleNetMovementSystem |
When a unit may take its next step, and where that step lands |
BattleNetCombatSystem |
Closing on something, swinging at it, and taking the blow back |
BattleNetTargetSelection |
What a unit may shoot at, and which of those it picks |
BattleNetProjectileSystem |
A shot from the cycle it is constructed to the cycle it lands |
BattleNetHarvestSystem |
A worker's round trip: out to the resource, and home with the load |
BattleNetConstructionSystem |
Putting a building up, and keeping it up |
BattleNetBuildingPlacement |
Where the computer decides to put a building |
Death keeps the living owner on a newly installed corpse or rubble record, then transfers ordinary vanishing scenery to neutral on its first decay-frame transition. The owner has already paid its roster removal before that handoff, so this changes the dying record without re-enlisting it. Timed revealers are not scenery and remain owned; air units without a corpse still disappear directly.
The boundaries are drawn along the questions a parity investigation actually asks. When the 52-case corpus reports that a peasant stepped a cycle early, the file to open is the movement system; when a grunt attacked where retail moved, it is combat; when a tanker never entered its platform, harvest. Before this, all of them were one 24,000-line file, and finding the rule meant knowing where in that file it had happened to be written.
Three rules keep the split honest:
- The collaborators are package-private and so are their seams. A
collaborator reaches
Worldstate directly, so members it needs stopped beingprivate-- but none becamepublic.engine.ai,desktopand the tests see exactly the surface they saw before, becauseWorldkeeps a one-line delegation for every entry point outside the package that used to call it. - No profile interface, no strategy hierarchy, no service locator. There
is one supported product, so runtime polymorphism would buy nothing and cost
a layer of indirection between a divergence and the rule that caused it.
Each collaborator is a
final classwith aWorldfield and nothing else. - A collaborator may name another.
combataskstargetswhat to shoot at andmovementhow to close;movementasksconstructionto abandon a pending build. The dependency graph is not a tree and pretending otherwise would mean inventing events the native engine does not have.
The rule was that a move may not alter behaviour, and each of the nine commits carries the same three-part proof:
- Structural. Every moved method body is byte-for-byte its original once
the
world.andWorld.qualifiers are stripped, and no qualified name shadows a local or a parameter inside the moved code. The exceptions are individually listed in the commit that made them --thispassed to an AI callback where it meant theWorld, and one record read through its own accessors rather than as fields. - Bytecode, for the profile removal only. Because the flag was
constant-folded, a correct specialization had to leave every method's
bytecode unchanged apart from the removed field, its
putfieldin the constructor and the two accessors.javap -c -pbefore and after says it did. - Behavioural. The 52-case Battle.net corpus is run at every step. All 52 first divergences hold, and all 52 full 1800-cycle Java semantic traces keep their SHA-256 -- so nothing moved even past the accepted parity frontier, where the gate itself would not have looked.
The ownership rule for the retired scripting language-free product is specified in the native BNE data boundary: edition-authored data belongs in the authenticated pack, while engine behavior and fixed application presentation belong in the game JAR.
launcher/ is the first process a packaged player runs. It owns
~/.chonkcraft/: verified graphics packs, complete game-version directories,
temporary import work and an atomically replaced choice file. The game is a
separate shaded jar under one version directory. Play starts that jar in a
child JVM with the selected pack and version content root, so installing a new
game version never replaces the running launcher and never touches a pack.
The compact first window contains only the active graphics pack, game-version
selector and Play. Its graphics control opens the ChonkPack manager, where
packs are imported by file chooser or drag and drop, selected, exported and
deleted. Pack creation records the selected source's release, container,
filename, byte length and SHA-256 in the manifest when those facts are
available; the temporary normalized directory is not treated as provenance.
The import boundary deliberately ends at the existing PackBuilder. A folder,
mounted CD, ZIP, StuffIt archive, ISO/Toast image,
BIN/CUE or IMG/CCD image is first normalized to a temporary installation that
InstallSource recognizes.
Cooked ISO9660, raw 2352-byte sectors and classic Mac HFS data tracks have
direct Java readers. External archive tools are a fallback for container
formats such as StuffIt; no external tool handles or converts the game assets.
Battle.net Edition's INSTALL.EXE is itself an MPQ and contains a second
War2Dat.mpq; the data module reads both in-process and presents their named
voices and movies as overlays on the classic numbered archives. This preserves
the edition's 16-bit unit effects and campaign narration instead of the
TOMEs' 8-bit copies, along with its 320x288, 16-bit stereo Smacker movies.
Its twenty 22.05 kHz stereo soundtrack recordings flow through the same
recorded-music interface as CD audio, and its 153 maps join the ordinary
AssetSource map list, which is what makes the single-player and multiplayer
menu entries available. Four BNE-only text tables are retained as standalone
supplemental pack assets.
The BNE music boundary is fail-closed. InstallSource requires the exact
twenty-name logical catalog, 22,050 Hz stereo PCM, positive frame counts and a
stable INSTALL.EXE:Music\\*.WAV origin. PackVerifier independently requires
twenty logical Opus assets at the 144 kbps policy and retains both the 22,050 Hz
source rate and Opus's 48 kHz decode rate. Main Menu and Orc Briefing are two
logical identities over one recording; the pack stores one physical payload
without losing either name, offset or length.
The pack becomes selectable only after PackBuilder reads it back and passes
its exact and perceptual verification.
Game updates use a signed pointer plus two immutable assets:
latest.properties, a content-addressed game JAR and a content-addressed
release-history catalog. The signed payload names each relative URL, exact byte
count and mandatory SHA-256. Downloads move into versions/ or the launcher's
offline history cache only after verification.
The player-selected authenticated pack is passed separately and is never part
of an application update.
The engine reads typed catalogs committed in the JAR for the roster, animation,
technology, buttons, presentation, tilesets, projectiles, construction, spells,
sound bindings, four campaigns and 52 mission wrappers. The mission catalog
contains 137 native trigger programs. Retail maps, media and ai.bin remain in
the authenticated pack. The former retired scripting language evaluator and generated-at-runtime
definition path were removed after complete differentials and protected gates.
Generated snapshots retain their actual historical GPL provenance; compilation is a runtime-detachment mechanism, not a claim of independent retail authority. New behavior is established from the pinned BNE executable and native captures.
runtime/ is a copy of seven-days-to-tomorrow/runtime, kept source-identical
so a sync is a plain diff. It supplies FixedStepLoop for simulation timing,
Java2DPipeline for backend selection, PlatformFullscreen, the SDL controller
hub and the PCM bus mixer. src/video and src/sound are ported onto these
rather than reimplemented.
Changes belong upstream and sync in; they do not start here. See runtime/README.md for the rule and what this port actually uses.
Packaging follows ChonkBlocker: pinned JetBrains Runtime 25, jpackage with a
jlink-trimmed runtime that retains bin/java for the replaceable child game,
macOS .dmg, Windows .msi, and a portable Linux .AppImage.
The port reads a Warcraft II installation, or an asset pack built from one. Both are configured by system property or environment variable, and tests skip rather than fail without them -- which means a green build can have verified almost nothing. That is documented in full, with the counts that tell you which kind of run you got, in development-setup.md and ci.md.
Extracted assets are never committed. They derive from the user's own copy.
The pack format is specified in asset-pack-format.md. A real pack built from a 1995 install is 157 MB against the installation's 1.04 GB, and the game plays from it alone with output byte-identical to a run off the installation.
maindat.war contains filler slots -- entries 28 to 32 in the DOS build --
whose offsets sit one byte apart while declaring multi-megabyte lengths.
wartool never notices, because it extracts by index from a fixed table and
those indices are not in it. Anything that sweeps the archive does notice, so
WarArchive rejects an entry with fewer than five bytes of room and returns
wartool's EmptyEntry placeholder for it.
The port plays. Campaigns, skirmish maps, combat, economy, construction, fog of war, computer opponents, spells, upgrades, sound, music, save and load, and lockstep multiplayer all run against a real 1995 installation or an asset pack built from one. All 52 campaign missions load, run and can be won.
The present release posture is summarized in STATUS.md. Executable tests and authenticated comparison evidence remain authoritative.
What is deliberately not ported: the map editor, replays, and the 741 Q
conversion rows.
The automated production gate runs two independent rendered game processes through the public HTTPS/WSS matchmaker and relay, forces exact map replacement, and proves 180 lockstep cycles converge to one world hash. A recorded match between two physically separate player machines remains useful field confirmation; it is no longer the only evidence for the public network path.
The test suite is 2,835 tests. On a fully configured CI machine 27 skip; with no external inputs 1,213 do. Both numbers matter and the difference between them is the subject of ci.md.