Part one covered the engine: a pure rules core, deterministic dice, EV-priced moves and free replay. That's the half a player never sees. This part is the half they do — the admin board builder, LLM opponents, the live table, and how the whole thing gets paid for.

The theme throughout: on a platform that already has accounts, permissions, an admin shell and a token economy, a surprising amount of "building a game" turns out to be configuration rather than code.

The board is data, and the economy is a form

The single most useful decision we made was refusing to hard-code the board. A board is a database record — squares, cards, colour groups, and the economic constants that govern play — which means the admin backoffice can edit it.

The VBWD admin board editor for the Funnel 40 board, showing tabs for General, Streets, Services and Card rules, and fields for name, display name, starting cash, salary per lap, fee policy, k_price, k_acquire, cap_pct and default seats.
The board builder in the VBWD admin app. Every constant from part one's pricing formula is a field here — and each carries the sentence explaining what it does.

Read that screen against the pricing formula from part one and the mapping is exact:

Note also the Display name field, set to "BizDevVibes". The game's presented name is config, not a constant in the source — so an operator can theme the same rules for their own audience.

Making these fields instead of constants is what turns balancing from a deploy into an experiment. Combined with the pure engine, you can simulate a change offline, then apply it in a form.

The BizDevVibes boards list in the VBWD admin backoffice, showing board records with status and actions.
Boards are ordinary records — list, duplicate, publish, unpublish. A board must be published before a match can be created on it, so you can iterate on a draft without touching live play.

The draft/published split matters more than it sounds. Because replay validates against a spec_hash, editing a live board's constants would invalidate the re-derivability of matches already played on it. Publishing is the boundary where that's made explicit.

Card rules without a deploy

The board editor's fourth tab is Card rules, backed by an effect system. Cards don't execute arbitrary code — they declare an effect from a registry of operations, which the admin API exposes at /admin/bdv/effect-ops and validates on save with validate_effect().

That's the standard trade for user-editable game content: a constrained, declarative vocabulary instead of a scripting language. You give up some expressiveness and you get validation, safety, and effects that remain replayable — an arbitrary script could read the clock or the network, and the determinism guarantee would die instantly.

Seats: humans, agents, and LLMs at the same table

A match is a list of seats, and a seat has a kind. Four exist: HUMAN, LLM (driven by a model), BASELINE (a cheap deterministic bot), and OPEN (waiting for a human).

Any unfilled seat resolves by a fill policy: fill with agents immediately, or hold the table open for humans for a set number of minutes. That one switch covers both "I want to play right now" and "I'm hosting a game with friends" without a second code path.

The BizDevVibes agents screen in the VBWD admin, listing house agent profiles.
House agents are admin records — the seeded roster ships three personalities: hard-closer, slow-nurture and deal-hawk. Each is a profile bound to an LLM connection, not a hard-coded opponent.

The LLM seats reuse the platform's central LLM Connection rather than holding their own API keys — the same connection registry that powers the CMS's AI features and the chat bots. Configure a provider once and every AI feature on the instance follows, including your game's opponents.

There's a security detail in seat construction worth stealing. The seat kind and display name come from the stored agent profile, never from the request body:

# The seat KIND and the display NAME come from the profile, never from the
# request: a client that named its own opponent could seat an agent under
# somebody else's identity, and the kind decides whether a model drives it.

Letting a client name its own opponent would let it impersonate another player at the table. Duplicates are allowed and numbered, though — three copies of one personality is a legitimate table, and the chat @-mentions seats by display name, so three identical names would make the feed unreadable.

The player UI is a frontend plugin

The user app side is a standard fe-user plugin registering two routes — /dashboard/bdv for the lobby and /dashboard/bdv/:matchId for the table — plus a Pinia store and its components: the board canvas, option cards, estate panel, trade screen, rent modal and game chat.

The BizDevVibes lobby in the VBWD user app, showing board and opponent selection.
The lobby. Because it's a plugin, "BizDevVibes" appears in the app's own navigation next to Subscription, Store and Invoices — the same sidebar you can see in the board screenshots.

That sidebar is the quiet point of the whole exercise. The game sits beside the subscription page and the store because it's the same application. A player already has an account, may hold a subscription, has a token balance and an invoice history. None of that was built for the game.

Matches also get human-readable slugs — lucid-gadget-49 in the screenshots — resolvable via /bdv/matches/by-slug/<slug>, so a table is shareable without exposing a UUID.

The negotiation window

One mechanic deserves its own note because it only exists thanks to the chat and payment layer already being there. When a player rolls, a negotiation window opens before they choose their move — visible in part one's screenshot as "Opponents may pay you to take the free sum."

Opponents can bid, in-chat, to keep you on fate rather than let you buy the move that helps you. The chat input carries a first-class command for it: /pay @name 500.

That turns a solitaire pricing decision into a table-wide negotiation, and it's a good illustration of platform leverage — player-to-player messaging and transfers already existed as primitives; the game just gave them a moment that matters.

Getting paid, and the line we drew

Monetisation runs through the platform's token economy, and the ordering in the code is deliberate:

# The charge happens BEFORE the match is created and in the same transaction:
# a fight that exists without a debit is free, and a debit without a fight is
# theft. Tokens buy the RUN, never in-game credits — those are created at the
# start of a match and destroyed at the end, and remain uncashable.

Two things there are worth generalising to any game with real money in it.

Charge and create in one transaction. A partial failure in either direction is a bug your users will find and your support team will pay for.

Keep the hard wall between real value and game value. Tokens are real; they buy a run. In-game credits are created at match start, destroyed at match end, and can never be cashed out. That boundary is a design decision with legal weight — a game economy that converts back into money is a different regulated product entirely, and the place to draw that line is in the architecture, not the terms of service.

Because tokens are a platform primitive, the pricing itself is config: agent_match_token_cost defaults to 10, and the endpoint returns a clean 402 with the user's balance when they're short.

What the platform actually saved

Concretely, the following were used rather than built: user accounts and authentication; RBAC, with the game's routes gated on bdv.play and bdv.boards.* permissions granted additively to existing access levels at install; the admin backoffice shell, into which the board builder mounts as a plugin; the token economy for monetisation; the central LLM connection for agent seats; player-to-player messaging and transfers; and plugin-owned Alembic migrations so the game's tables ship and roll back with the game.

What we wrote was the engine, the board model, the API, the board builder and the player UI. That is the game — and roughly speaking, that's the right ratio.

The honest limits

This is a turn-based board game, and that is the shape of game the architecture suits. The state transitions are discrete, a turn is a request, and an authoritative server resolving moves is exactly right. A real-time action game with a 60Hz tick and client-side prediction is a genuinely different problem and not what this is built for.

The purity constraint is real work. You assemble state, resolve, then persist — you never reach for the database mid-move. It pays for itself in testability and replay, but it's more structure than the naive approach.

And a configurable economy is a responsibility as much as a feature. Six interacting dials mean six ways to produce an unbalanced board. That's why the balance harness exists, and why the engine being runnable without infrastructure matters so much: tuning is a compute problem instead of a deploy problem.

The read

A board game with priced moves, a player-to-player fee economy, LLM opponents, in-game negotiation and token-based monetisation runs as three plugins on a platform whose day job is subscriptions and commerce. The game logic is genuinely bespoke — an EV pricing engine isn't something you get from a framework. Everything around it wasn't.

If you're evaluating what to build on VBWD, that's the useful takeaway: the platform doesn't know what a game is, and didn't need to. It knows about users, permissions, money and plugins, which turns out to be most of what a game needs that isn't the game.

Start with part one for the engine, or explore the plugin catalogue, the architecture overview and the developer docs.

Learn more about VBWD

VBWD is a self-hosted, source-available platform for building subscription products, marketplaces, and AI-powered apps. Explore it further: