
A UK city, built from zero
Rikki’s UK roleplay city, No. 1 in the UK within a month and the home of everything that followed. The community side is on Communities.
A GTA V roleplay server where the store, every delivery and the city itself run with nobody in the loop.

GOV began as a storyline inside KNG Estate, the UK server Rikki founded. The figures above are KNG’s.

Rikki’s UK roleplay city, No. 1 in the UK within a month and the home of everything that followed. The community side is on Communities.

Inside KNG, Rikki’s faction steered the city’s roleplay from behind the scenes. Players followed its storylines, and it grew into a server of its own.

The Gov became its own server. This time the technology was the point: a store that delivers on its own, and an AI that keeps the city’s story moving while the staff sleep.
Real screens from the game, the store and the console; test accounts are marked. Pick a step.


£15, £25 or £40 a month

The city loads behind GOV’s own screen: rain on an estate, lines like “Welcome, operative”, a mute key. Built for the game’s older browser, it cleans up after itself when the player arrives.
Each player picks a character at border control: job, ID, money and hours played. A character in prison or dead says so before the player walks in.
Three clearance tiers a month, plus exclusive IDs and vehicles. Checkout runs through Tebex, which pays out and hands the order to the server.
What a player has bought, in one place: rank, perks, when they expire, vehicles, IDs and orders.
The AI city brain on the development server: it reads the city, finds what is wrong, asks the model for moves and runs only the ones it is allowed to.
Tebex takes the payment and runs one command on the server. Everything after that is GOV’s own code.
A sale at any hour is delivered the moment Tebex confirms it: no ticket, no staff member, no wait.
The command does nothing unless it comes from the server console, which only the store uses. A player who types it gets nothing.
Expired tiers are removed on a timer, the player is told and the change is logged. Nobody keeps a spreadsheet of who paid until when.
RegisterCommand('gov_vip_deliver', function(source, args)
if source ~= 0 then return end
local citizenid = args[1]
local tier = args[2]
local days = tonumber(args[3])
local txnId = args[4] or 'unknown'
local pkgName = args[5] or 'VIP Package'
local amount = tonumber(args[6]) or 0
local success, err = GroupService.setVipTier(citizenid, tier, days)
if success then
CreditService.recordPurchase(citizenid, txnId, pkgName, 0, amount)
local src = Cache.citizenIdToSource[citizenid]
if src then
exports.qbx_core:Notify(src,
('Welcome to VIP %s!'):format(tier:upper()), 'success', 10000)
end
end
end, true)The delivery command as it runs on the server, shortened: the error messages are cut and one long line is wrapped. The highlighted lines are the four steps in the drawing.
The same route assigns exclusive IDs and tops up GOV credit, each with its own command.
The city brainIn the story GOV secretly runs the city. In the code an AI does, with moves players feel but can’t trace.
| SWITCHED ON · WHAT PLAYERS SEE | |
|---|---|
dispatch_tip | An anonymous tip in police dispatch |
economy_adjust | The drug market tightens or loosens |
broadcast_news | A City Wire story on their phone |
recruit_agent | An encrypted message from an unknown number |
assign_mission | A task for a recruited agent, paid in crypto |
destabilise_faction | A supply cut, a rival’s tip, planted evidence |
| PLANNED, SWITCHED OFF | |
spawn_event | Waits for the event system |
plant_intel | Waits for the link to the police computer |
open_contract | Waits for the dark market |
Players with ten hours in the city can be recruited as GOV agents, five at a time at most. Nobody knows who else works for GOV: the paranoia is the feature.
A game server is a hostile place: players test every command they can find. The choices follow from that.
| Choice | WHAT WE CHOSE | WHAT IT COST LATER |
|---|---|---|
| 01A small model inside a hard whitelist2026OpenRouter | The city brain runs on Gemma through OpenRouter’s free tier while the server is in development. The model is never trusted: only listed moves run, two per cycle at most, and it remembers eight events. | The free tier hits its rate limit fast. And the model answers in two shapes, a list or an object holding a list, so the code accepts both. |
| 02Delivery only from the consoleMARCH 2026 | The command that delivers a purchase checks who sent it. Anything but the server console gets nothing, so a player who learns the command cannot use it. | Two resources once registered the same delivery commands. On 30 May they were folded into one, so a sale is delivered once. |
| 03Built for the game’s old browserMARCH 2026 | The game draws its screens with Chromium 103. The build targets it, animates only position and opacity, and every screen clears its timers and media when it closes. | Modern JavaScript failed without a word until the target was set. One line of CSS, color-scheme: dark, painted the whole screen black. |
| 04No code we cannot readJUNE 2026 | On 2 June every encrypted third-party script was removed. What runs on the server is code we can open. | That left clothing, jobs, the casino, the estate agent and the doors to rebuild or replace. |
Tebex sells and OpenRouter thinks. What GOV asks them, what comes back and what is written down; keys redacted.
When the store opens, the browser asks Tebex for every category with its packages inside, in one request. The address carries the store’s public token: it can read the catalogue and open a basket, nothing more, so it is safe in the browser. Tebex wraps the answer in a data field; the code unwraps it, and if Tebex is down it logs the error and shows an empty shop instead of a broken page.
GET https://headless.tebex.io/api
/accounts/‹public token›/categories
?includePackages=1
export async function fetchCategories(): Promise<TebexCategory[]> {
if (!token) {
console.warn('[Tebex] No token configured — store will be empty.');
return [];
}
try {
const res = await fetch(
`${API_BASE}/accounts/${token}/categories?includePackages=1`);
if (!res.ok) throw new Error(`Tebex API ${res.status}`);
const data = await res.json();
return data.data ?? data ?? [];
} catch (e) {
console.error('[Tebex] Failed to fetch categories:', e);
return [];
}
}if (!token)No token configured: warn in the console and show an empty store.fetch(…)The request above: one GET, with the query that nests packages in categories.if (!res.ok)Any status outside 200–299 becomes an error that names the status code.data.data ?? dataUnwrap Tebex’s data envelope, or use the body as it is.A category holds packages, and a package is what a player buys: a name, a price and a currency. The three VIP tiers are three packages in the VIP category, not one item with options; the site reads which medal to draw from the package name.
Exclusive IDs work the other way round. The package is the range (single digits, two digits, three), and the number the player picks is the variation: it travels with the basket line as variable_data. Numbers already sold come from a Firestore list, so they show as taken before anyone pays.
{
"data": [
{
"id": 1001,
"name": "VIP",
"packages": [
{ "id": 2001, "name": "GOV || Bronze",
"base_price": 15.00, "currency": "GBP",
"category": { "id": 1001, "name": "VIP" }, … },
{ "id": 2002, "name": "GOV || Silver",
"base_price": 25.00, "currency": "GBP", … },
{ "id": 2003, "name": "GOV || Gold",
"base_price": 40.00, "currency": "GBP", … }
]
}
]
}const getTierFromName = (name: string): 'bronze' | 'silver' | 'gold' => {
if (name.toLowerCase().includes('gold')) return 'gold';
if (name.toLowerCase().includes('silver')) return 'silver';
return 'bronze';
};POST https://headless.tebex.io/api/baskets/‹basket ident›/packages
{ "package_id": 3001, "quantity": 1, "variable_data": { "chosen_id": "777" } }Each cycle, the server posts the city’s state to OpenRouter’s chat endpoint. The key rides in an Authorization header and lives in a server setting, never in the code or a browser. The request asks for JSON only, and the brain’s instructions fix the shape: at most two action objects, each with a type, a mandate, a payload and a private reason.
The reply is checked twice. Did the request succeed? Does the content parse as JSON? Either answer “no” means no move this cycle, and the console says why.
POST https://openrouter.ai/api/v1/chat/completions
Authorization: Bearer ‹key from the gov:openrouter_key server setting›
Content-Type: application/json
local body = json.encode({
model = Config.Brain.Model,
messages = { { role = 'system', … }, { role = 'user', … } },
temperature = 0.7,
max_tokens = 512,
response_format = { type = 'json_object' },
})[
{ "action_type": "broadcast_news", "mandate": "…", "event_type": "…",
"payload": { "headline": "Unclaimed Cargo Found at Docks",
"body": "…", "tone": "neutral" },
"reasoning": "…" },
{ "action_type": "economy_adjust", "mandate": "…", "event_type": "…",
"payload": { "direction": "up", "magnitude": "small",
"reason": "Increase incentive for early session activity." },
"reasoning": "…" }
]Two logs make the system checkable. Each sale is a row keyed by its Tebex transaction ID, and that key is unique: if the same order arrives twice, it is recorded once.
Each brain move is a row holding the city as it was, the move and its payload. Five minutes later the same row gets a score from −5 to +5 and the change in players online. That score is what the brain reads back as “worked” or “backfired”.
MySQL.insert.await([[
INSERT IGNORE INTO gov_vip_purchases
(citizenid, tebex_txn_id, package_name, package_id,
amount, status, delivered_at)
VALUES (?, ?, ?, ?, ?, 'delivered', NOW())
]], { citizenid, txnId, packageName, packageId, amount })
-- gov_groups/server/storage.lua: the table
tebex_txn_id VARCHAR(100) NOT NULL UNIQUE,CREATE TABLE IF NOT EXISTS `gov_brain_events` (
`mandate` VARCHAR(32) NOT NULL
COMMENT 'faction|economy|rp_depth',
`city_state` JSON NOT NULL COMMENT 'snapshot at decision time',
`action_type` VARCHAR(64) NOT NULL,
`action_payload` JSON NOT NULL,
`outcome_score` TINYINT DEFAULT NULL
COMMENT '-5 to +5, NULL until scored',
`player_delta` SMALLINT DEFAULT NULL
COMMENT 'player count change after action',
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
…
);Two days after the brain was added to the development server, its live console repeated the same lines every minute: API error 429, then “AI returned no actions this tick”. The city was getting no moves at all.
A 429 means too many requests. The brain was on the model’s free tier, which caps how often it can be called, and it was calling once a minute. The status check printed the code, so the cause was clear from the console alone. The fix was one line: call every five minutes instead.
09:25:07 [gov_brain] Consulting AI for mandates: faction, rp_depth
09:25:08 [gov_brain] API error 429:
09:25:08 [gov_brain] AI returned no actions this tick
09:26:08 [gov_brain] Consulting AI for mandates: faction, rp_depth
09:26:09 [gov_brain] API error 429:
09:26:09 [gov_brain] AI returned no actions this tick- TickInterval = 60 * 1000, -- 1 minute
+ TickInterval = 5 * 60 * 1000, -- 5 minutes (avoids free-tier 429s)What a staff member does, what happens with nobody watching, and what the world is made of.
Rikki Chandler founded KNG Estate with one partner, led The Gov, then founded GOV Roleplay.
On GOV, Rikki designed the systems and built them by directing AI coding agents, reviewing every change before it reached the server: the store and its delivery, the player’s HQ, the staff panel, the character select, the loading screen and the city brain.
The gang, drug and crafting systems came from the server’s other developers. The clothing packs are open-source work by their makers, chosen and integrated for GOV.
Each figure carries its source. GOV’s own player numbers come after launch.
SOURCE · RIKKI CHANDLER’S CV, 2026
SOURCE · GOV SERVER CODE: GOV_GROUPS, GOV_BRAIN
SOURCE · GOV SERVER-FILES CONTEXT
NEXT CASE Rocks Investments
Read the caseWhether you are hiring or building, the first step is the same: one conversation with Rikki Chandler.
Rikki connects customers to systems, fixes what breaks and explains it so it sticks.
Tell us the one thing that should change. We start from that question and a written test of how we will know.
Every door leads to the same person.