jdVOID Part II: The Card Factory
Part I was the game. This is what happened in the five days after, and it is not more content. My son wants three to six new cards a week. That is a tooling problem, not a card problem.
It installs like an app
A web manifest, a service worker, three icons rendered from the key art. Tap once and jdVOID sits on the home screen with no address bar. No store, no wrapper, no second build.
The worker splits traffic three ways, and that split is the whole design:
/api/is never cached. A cached match state would be a disaster.- The 10 MB WebAssembly runtime is served from cache and refreshed in the background. It only changes on a runtime deploy.
- Everything else is network first, cache as fallback. A deploy has to reach players at once.
A card designer, in the browser
A ship is three to eleven signed distance primitives on a 40 unit grid. The designer draws them, you drag them. Then the values, the twelve traits the rules actually read, the card colour, the description in two languages. The preview is the game's own rasterizer ported to JavaScript, so it shows what the game will draw rather than something close to it.
It writes nothing. Drafts live in localStorage, the output is a JSON block, and getting a card into the game means pasting that block into cards.json and deploying. I built the server side publish flow on paper and then did not build it. Family policy: no wild growth.
Two cards, the real rules
The client is jdBasic in a browser, so the rules module can just run there. sandbox.jdb drops two cards facing each other and plays cards into arena.jdb. No second engine to drift.
Same duel, desktop and WebAssembly: LASER wins with 100 hull left after 9.3 seconds. Identical to the tenth of a second and the hull point.
The beam in that picture is a new card field. Until now the only thing that decided what an attack looked like was reach: over two a travelling bolt, under it a melee slash, and always the same yellow. A card can now ask for SHOTFX: "BEAM" and gets a standing line in its own colour.
One card against all the others
The question a new card raises is not whether it beats the DROID. It is what it is too strong against.
$ jdbasic duels.jdb IRON tmp/iron.json
IRON (6 Elixier) gegen alle anderen
Gegner Elix Ausgang Rest Zeit
BRUTSCHIFF 5 BRUTSCHIFF gewinnt 770 32.5 s
GLEITER 4 IRON kommt durch 1915 12.4 s
KOLOSS 5 KOLOSS kommt durch 1140 8.9 s
PLASMA 4 Zauber 325 nimmt 325 von 3400
RAMME 4 RAMME kommt durch 720 3.7 s
TITAN 7 IRON gewinnt 1150 17.9 s
Bilanz: 11 gewonnen, 3 verloren
gegen gleich teure oder teurere: 1 von 1 gewonnen
"Walks through" is not "wins", and the difference is the whole point of a card that ignores troops. The table keeps them apart.
Replays, and what if
A recorded match is its two decks, the card levels and the cards that landed. Inputs, not states. Replaying it has to end the way it ended, which makes every recording a determinism test that runs itself.
Each recording carries a fingerprint of the card table it was played under, so a replay that diverges can say whether the rules moved or the recording is incomplete. Checkpoints every 300 ticks name the second where it started to differ.
Then whatif.jdb: change one number, replay every usable recorded match, and count how many winners flip. A balance change measured against matches that were actually played instead of a feeling.
What the tools said that we did not know
- TITAN cannot stop a KOLOSS. KOLOSS has
ONLYTOWERSand ignores troops, and at speed 1.2 against 1.0 the TITAN can never catch it. It shoots at its back and falls behind. - A LASER beats a DROID with two and a half times its hull. Reach 5 against 1.2. The DROID never lands a hit.
- My son's first card was 57 percent tougher per elixir than anything in the game, and beat the card that costs one more. Dropping its 1000 point shield to 400 changed nothing in the sweep. Neither did cutting its hull from 2400 to 1400. The tipping point was near 1300, and below that it over corrected to 8 wins and 6 losses.
- CHARGE counts tiles, not seconds. The run-up accumulates distance and every blow resets it.
The last one is why the designer's help column was read out of arena.jdb rather than remembered.
Play it
Still live at vvss.jdbasic.tech, now installable to a home screen. Source under jdb/demos/games/basicroyale in the jdBasic repo, and the language itself at jdbasic.org.
Five days. Zero new cards from me. My son has a factory.