Overview
A turn-based browser RPG. You make a character from one of eight races and nine classes, walk a 52 by 52 tile kingdom, search its fields and cut its trees, fight whatever lives there, carry the loot to one of eight towns, craft it up through a tree of trades or sell it, buy better gear from the smiths, and take your kit into the arena against other players. A PHP API in apps/api, a React client in apps/web, MySQL underneath.
The core loop
Current state as of 22 Sep 2026
Done
- Combat rework. Five elements, eleven statuses and an armour pool, all through one pure resolver (
combat.php) that the API, the simulator and the tests all call. - 36 new class skills in English, 4 per class plus Heavy Blow, Absolve and Flee. Button text is derived from the formula by
describe()(migration 0029). All 38 kit rows match it exactly. - Sandstorm slots and a High-Low table new Gambling now levels: a level raises the stake limit, never the odds. A High-Low hand left open now resumes. See Gambling.
- Feel for everything new Chopping, digging, building, planting and sleeping each have their own shake, vibration and sound, and the field actions play a short scene. In battle, a third of hits shake the screen, criticals get a stamp, and taking damage bleeds red in from the edges.
- Artifact upgrades new An extra backpack and potion-kit rung, sold at the homestead. The skinner's middle potion kits no longer ask for sand.
- Sand is this world's gold new Sand Ore is no longer crafted. It is found one dig in a hundred in the Deep Vein and on the Overlord, and sells for 202, the dearest raw material in the game.
- Luikach Mine new Two entrances, three shafts that open by Mining level, and a daily number of digs. Mining and Farming now level up. See Mine.
- Homestead new Buy a Land Deed, settle any open field, then build a cottage, workshop, orchards and storehouses out of crafted goods. Plant seeds that take or fail after 10 moves. See Homestead.
- One battle board for both modes. PvP used to fight with the Chirreaja's Maul (action 15, hardcoded). Both modes now use the same fighter cards, side by side at every width, with armour on the health bar, statuses and Spell. The class kit shows as
SkillButtons. - A battle log shows who used which skill and what it did, including the opponent's turn (migration 0030).
- A unique icon for every skill new All 68 battle actions have their own game-icons glyph (migration 0032, kept next to each skill in
kits.py). Flee is a tile in the kit on both screens. - Inventory tabs. Bag, Potions and Gear are labelled tabs, at the bottom on a phone and in the header on desktop.
- Crafting economy. 67 recipes, 34 new goods, prices derived from field visits. Four rules are checked by the generator and by
CraftEconomyTest. The town-price arbitrage is gone. - Economy export. Every active item renders an icon. The only blanks are the placeholder rows.
- Audit 185/185 clean, 24/24 interaction flows, phpunit 210 tests, phpstan 0. Locales in EN, DE, HR and RU.
Open
- No flow drives a battle to its end. Nothing presses a skill in either mode, so statuses, armour and stacking are only covered by the simulator and by SQL-seeded screenshots.
- Enemies have no race, so the element edge never fires in PvE.
enemyonly hascombat_level,nameandpayout. Fixing it takes a one-column migration. - PvE is unmeasured since the rework. The 61–76% curve dates from migration 0020. The
--pvearm was removed fromsimulate-combat.phpin c025c884, but the flag is still accepted and does nothing. - Build spread is structural. It is 65.5 pts at level 1 after the rework (53.3 before). The weakest builds sit at 16–21%. Needs a different lever, not another tuning pass.
- Every item can be bought anywhere.
/purchase-actionsells any item id at its buy price (twice its sell price), whatever the vendor stocks. That includes the goods a homestead is built from. - PvP has two ways to concede: the Flee tile, which works on your turn, and the Forfeit button in the bar, which works any time.
- Every enemy can now be met. The Overlord holds the Sand Citadel at 46,5, the Skeleton the mine, the Bunny the sand and the Giant the Blackwood groves.
- Rotate the SMTP password (owner). api-gaps A-5 and C-5, the health-clamp migration, the P2/P7 scorecard, the second theme pass and provisioning are still to do.
Characters
A character is a race plus a class. Each has a 300-point stat block on top of 200 base health, spent differently. The race also sets your element profile: each race resists one element and is exposed to another, at ±20% damage (COMBAT_ELEMENT_EDGE = 0.20). The class decides your kit.
Races
Element profile by race
Damage taken, by element. Two races resist physical and two resist shadow. Nobody besides the Dwarf resists fire, and only the Troll resists frost.
Classes and their kits
Every class gets the same shape: a basic, a signature, a control and a guard. Everyone also keeps Heavy Blow, Absolve and Flee, so a kit is seven buttons. Scores are the PvP duel simulator at levels 1, 5 and 10 (target band 40–60%).
Battle
A fight is a series of rounds. Both sides pick an action, both actions are planned against the board as it stood at the start of the round, and then both are applied. Agility decides who applies first, which only matters when the round is lethal. No status takes a turn away. Effects make a turn worse but never skip it.
How one blow resolves
combatStrike() runs one order and only one. The API, the simulator and the tests all use it.
- Base % of the attacker's stat
dmg:170reads Damage,spell:135reads Spell. A stat of 1000 is one full point. - × level ratioBetween the two fighters. In a level-matched fight it is 1.
- × element against race±20% if the defender resists or is exposed to the action's element. Always 1 against a monster.
- × vulnerable on the defender+35% taken.
- × weaken on the attacker30% less dealt.
- × critical, from agilityChance up to 50% (half-saturates at 170 agility), ×1.90.
- − defence mitigationSaturating curve, capped at 62% (half-cap at 150 defence).
- − armour absorbs what is leftThe guard pool soaks it before health does.
- → health±15% variance, and at least 1.
- → thornsSends 30% back to the attacker if the defender carries thorns.
The numbers in combat.php
Armour and guard
A guard:N clause grants an armour pool that scales off the caster's Defense. Armour soaks damage before health does, and half of what is left decays at the end of each round (COMBAT_GUARD_DECAY = 0.5). Spending a turn on it pays off, but you cannot stack it into a wall.
Poison and bleed are marked as going through armour, and armour soaks half of such a tick. The first design let them ignore armour completely (after Divinity), which made armour worthless against half the kits. Burn does not go through, so burn and poison are different tools.
Stacking: things that tick stack in strength, up to 2× one application (COMBAT_STACK_CAP). Things that modify stack in duration. A weaker application never shrinks a stronger stack.
The five elements
The eleven statuses
PvP and PvE are not the same fight
| Arena (PvP) | Field encounter (PvE) | |
|---|---|---|
| How it starts | Queue in a town's Arena, matched against another character | A roll when you step onto a field whose type has an enemy (10–17.5%) |
| Opponent's kit | Their class kit: 4 skills, Heavy Blow, Absolve, Flee | 2–4 monster actions, picked at random by weight (enemy_battle_action.chance) |
| Element edge | on ±20% from the defender's race | off Enemies have no race, so the board shows null for their profile |
| Flee | Concedes the battle, same as /pvp-forfeit | Rolls combatEscapeChance(), 10–90% on the agility ratio. A failed roll costs the turn. An escape earns 15% of a win's experience |
| Turn clock | 180 s per turn (PVP_TURN_TIMEOUT_SECONDS) | none |
| Reward | 10 marbles × loser's level. XP ×0.25 for a win, ×0.05 for a loss | enemy.payout (0–25 marbles), XP from the enemy's level, material drops, and a 50% chance of a generated equipment piece |
| Screen | Shared board: turn banner with round and 180 s clock, both fighter cards, battle log, skill dock. Flee is marked "Forfeit" | The same board, with the enemy's portrait in its card. Flee is marked "Escape roll" |
Balance, as measured
From tools/simulate-combat.php --rules=current, re-run for this atlas: every race × class build against every other in both turn orders, 8 repeats on seed 20260921. That is 40,896 duels per level, naked (no gear). Score counts a draw as half a win. The target band is 40–60%.
By class
By race
Extremes (race + class)
How the class spread came down
Per tuning pass in the rework (points between best and worst class, level 1):
Race spread went 30.3 → 15.9 → 13.8. That came from one word: poison scales off Damage, not Spell. Before it, the race table simply ranked races by Spell. Lowering the element edge from 0.25 to 0.20 changed nothing.
PvE, before the rework
Win rate against each enemy at its own level, wearing a full Poor set (migration 0016, docs/balance.md). After 0020 the level-matched range was 61–76%, with the Overlord at 42%.
--pve is still in the getopt list but nothing reads it, so the PvE numbers on this page describe the old rules.Skills
All 36 class skills plus the three every class keeps. Each card is drawn like the in-game SkillButton: the element on the left edge and the icon, then the name, then the effect chips from battle_action.description (rounds:key pairs, with a number when the effect lasts more than one round). The raw formula is underneath.
Formula grammar
| Clause | Meaning | Button chip |
|---|---|---|
dmg:N | Strike for N% of Damage | Damage |
spell:N | Strike for N% of Spell | Damage |
heal:N | Heal self | Heal |
recoil:N | Costs the caster health | Recoil |
clear:0 | Cleanse your own statuses (Absolve, Cleansing Light, Purify) | Cleanse |
burn|poison|bleed|regen|chill|curse|sunder:N:R | Apply a status with strength N for R rounds | status name, with R |
vulnerable|weaken|thorns:R | Flag statuses. Strength fixed by the rules, R rounds | status name, with R |
guard:N | Armour pool. Spent, not timed, so it never shows a count | Armour |
buff:spell:N | Flat stat buff (Refrain) | SPELL+ |
Items and equipment
Every row in item, with the icon the client draws: the glyph from Icon.tsx's map, tinted by the image prefix (Iron_, Ebony_…), inside a rim in the rarity colour. Pick any item to see where it comes from and what it feeds.
Equipment sold by the smiths migration 0013
Twenty pieces, ten per smith. There are seven slots: Helmet, Armor, Belt, Pants, Feet, MainHand and OffHand, plus AnyHand for staves. Town prices apply on the buy side only.
Loot equipment: stat budget per full set
StatsGenerator spends a points budget per rarity on what the slot is for: a helmet defends, a sword cuts, boots make you quick.
Potions
Upgrade ladders
The skinner sells backpack and potion-kit upgrades for marbles plus crafted materials. The bank sells deposit-box packages for marbles only. The bar shows the price against the top rung.
Crafting and the economy
Before migration 0021, every recipe in the game was worth less than its parts. Now there are 67 recipes across the trades, and each price is derived from the thing the game actually rations: field visits. You get one action per field per day, so time is the currency under the marbles.
The rules the generator and the test both enforce
What a field visit is worth
| What you do with a visit | marbles / visit |
|---|---|
| Sell what you found, as is | 2.0 |
| One craft above it | 4.0 |
| The middle of a chain | 5.6–6.4 |
| The top: Expedition Pack, Travel Ration, Caravan Crate | 7.2–8.0 |
Per backpack slot the gap is much wider. An Agarwood Beam is worth 25 in a slot and a Trade Caravan Crate is worth 1,385. A backpack of twelve holds 300 against 16,620.
Marbles per visit, by depth in the tree
Each dot is one good. Depth 0 is sold raw and each step up is one more craft. Rule 4 says this has to rise the whole way up, and it does.
What the money path actually was
- The best path was not crafting. Town price modifiers (0.5, 1 or 1.5) applied to buying and selling. Twelve needles bought in Coristar and sold in Angalash turned 1,000 marbles into 1,036 with no risk. The modifier now applies to buying only.
- The chain used to peak in the middle. The wood chain paid 15.6 marbles per visit at the beam and 5.3 at the masterwork. Pricing by visits with a wage that rises with depth fixed it.
- Raw prices ignored scarcity. A 40% Ebony Log fetched 29 while a 3% Oyster with Pearl fetched 4. Every gathered material is now worth about 2 marbles per visit it costs to find.
- Metal needed a source. Iron and coal are rare finds on rocky ground until the mine exists.
The deepest chains
Every recipe, by trade
Where raw materials come from
Field drops by action and field type (field_item), with the chance per visit and the number of fields of that type.
The merchant sells eight goods at a fixed price: .
Homestead
newA field of your own. Buy a Land Deed from the merchant, stand on any open field outside a town and settle it. Every building on it is paid for in crafted goods, which gives the crafting tree somewhere to go. Before this, the only thing to do with a finished good was to sell it. Seeds and saplings, which the fields already drop, finally grow into something.
How it plays
What you can build
Each cost is shown with the value of the goods it consumes, at their sell prices. The orchards and storehouses come in tiers: each tier needs the one before it and replaces it.
What grows
Whether a seed takes is rolled when you plant it and revealed after moves. A move is one step on the map. The odds are shown before you plant. Fertile Soil worked in adds points, capped at %. Planting value is what the seed is expected to turn into at raw prices (30 days for a fruit tree). The last column is what the seed sells for.
Storage against the bank
The bank rents deposit box room for marbles, and the homestead's is built from goods. The total cost of each tier is held between half and one and a half times what the bank charges for the same number of slots. The shaded band is that range.
The checks homestead-economy.py enforces
The tool generates migration 0031 and refuses to write it if any check fails. The first run showed a sapling was barely worth planting (14 against 13 for selling it), so timber was raised to six logs.
Luikach Mine and the trades
newThe ore items have said "a product from the deep depths of Luikach Mine" since the first migration, but until now the mine had no field and no screen. It now has two entrances at the foot of the mountains and three shafts that open by Mining level. Mining and Farming, which every character has had since creation at level 1 forever, now level up: digging pays Mining, and working the homestead pays Farming.
The shafts
Each dig rolls the shaft's table once, puts the find in the backpack and pays Mining XP, whether it found anything or not. Each entrance allows a set number of digs a day, counted separately. The value is the expected marbles per dig, at sell prices.
The trade curve, shared by Mining and Farming
Total XP to reach each level. It is gentler than combat's, whose top is 171,000, because a trade earns in tens.
What each level buys
The checks mine-economy.py enforces
Gambling
newTwo tables at the gambling house. The Sandstorm slots are three reels of the world's own ground, with sand as the jackpot. High-Low is a guessing game at a card table. A Gambling level raises how much you may stake, never the odds, so the house keeps the same edge on everyone.
The Sandstorm slots
Each reel has the same stops. Three of a kind pays that symbol's multiple of the stake. With no three of a kind, sand anywhere pays a little. The return shown is exact: slots-economy.py enumerates all 216 combinations.
What a Gambling level buys
The most you may stake on one spin or one High-Low hand. Every stake pays a point of Gambling XP per marbles, up to a play. At High-Low it is paid on the first guess, so a stake collected back untouched earns nothing.
High-Low
Guess whether the next card, drawn from 2 to Ace, is higher or lower. A right guess adds the current card's bonus for that direction, per tenth of the stake, to the pot. A wrong guess or a tie loses everything. You can collect at any time. The less likely a guess, the more it pays. A hand left open stays open and resumes when you come back.
Enemies and PvE
Ten monsters, levels 1 to 8. You meet one by stepping onto a field whose type has an entry in field_enemy. Beat it to collect its payout, a roll on its material drops (enemy_loot) and, half the time, a generated piece of equipment whose rarity rises with the enemy's level.
Rarity of the equipment drop, by enemy level
calculateItemRarity(). 50% of kills drop a piece, in a random slot (14% each).
Monster actions
World and screens
One region, Kingdom A: 52 × 52 = 2,704 fields of 23 types, drawn here from the field table in the same terrain colours as the client's FieldImage. Hover or tap a tile to see what it is.
Legend
Towns
Each town has its own price modifiers. Since migration 0022 they apply to buying only, and every vendor pays the same when you sell.
Field actions
Screens
Every view in apps/web/src/views and its route in Main.tsx, grouped the way a player meets them. Names are from en.json.