# Changelog
All notable changes to this project are documented here.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Planned
- A real CreateWorldScreen tab for choosing the starting-city type (the `spawn_city_structure_tags`
list covers the function for now; default is the village tags → effectively random nearest).
- Per-variant city tags (so the starting city type can be e.g. only "fighting" bca villages).
- **Dive item** (Phase 9): underwater breathing/mobility to travel the aquatic routes (HM Dive-style).
## [0.45.0] - 2026-06-14
### Fixed — No route toast while inside a town/structure
- **Route-entry toasts are now suppressed whenever the player stands inside any generated structure**
(village/city, gym, …), not just within a fixed 64-block radius of the recorded centre. Fixes the
"route toast while in a city" report for villages whose footprint extends past that radius
(`getStructureContaining` check in `RouteTracker`).
## [0.44.0] - 2026-06-14
### Fixed — Downed wilds stay put and can't be re-battled
- **A defeated wild lying in its Defeated-Catch window can no longer re-enter combat.** Any battle that
would include a downed wild is cancelled at `BATTLE_STARTED_PRE` — it can only be re-caught, not
fought again.
- **The downed body is kept inert.** Its freeze (AI off, navigation stopped, zeroed velocity, lying
SLEEP pose) is now re-asserted every second, so flows that re-enable AI or recalculate the pose can't
make it wander or stand back up.
## [0.43.0] - 2026-06-14
### Added — Differentiable road intersections
- **Crossings now get a contrasting plaza** so they stand out from the road ("haz las intercepciones
más diferenciables"): where two roads cross (out in the open, away from cities), the junction is
paved as a stone-brick ring around a polished-andesite floor, radius 3 — wider than the 2-wide road
— in addition to the existing four-line junction signpost. Skips aquatic crossings.
## [0.42.0] - 2026-06-14
### Added — Mineshaft tunnels through mountains
- **Routes now bore tunnels through terrain too costly to go around.** When a road is connecting to a
known node and a segment's surface path would climb ≥ `TUNNEL_RISE_TRIGGER` (12) blocks — or no
surface path exists at all — the road bores a straight, constant-height **mineshaft** toward the
target instead of contouring up and over the mountain. (The user's explicit exception to the
never-terraform rule: "si una ruta ya ve un nodo pero el costo es demasiado alto… como un mineshaft".)
- The tunnel is 3 wide × 4 high, floored with the route's path block, with an **oak frame + beam + a
hung lantern** every 5 cells for support and light. Carved blocks are removed without dropping items.
- It **self-terminates**: each segment re-checks the surface, so it keeps tunnelling while the mountain
stays ≥12 above the bore and resumes normal surface paving once the terrain descends to meet it.
- Won't bore through other structures (a centreline cell inside a foreign structure cancels the tunnel,
falling back to the normal stall/retry).
## [0.41.0] - 2026-06-14
### Changed — Routes flatter + kept off the coast
- **Routes now keep a buffer inland from shorelines** ("aléjalas de la costa"). A bounded multi-source
BFS from water cells fills a coast-distance gradient (`Snapshot.coastCost`); the A* pays
`(COAST_RADIUS - dist) * COAST_COST` for cells within 6 of water, so roads stay ~a few blocks off
coasts instead of hugging them — while still able to approach/bridge water when there's no
alternative (the penalty is finite). Water cells themselves are unchanged (they already pay
`WATER_COST`).
- **Routes prefer flatter ground**: the per-step climb penalty (`SLOPE_COST`, applied to the square of
the height change) is raised 5 → 7, so roads contour around hills on gentler grades.
- A* optimality preserved: both penalties only raise real step cost above the (still admissible)
heuristic.
## [0.40.0] - 2026-06-13
### Added — Hunger toggle + PC-only-at-block enforcement
- **Hunger toggle now enforced.** When the world's `enableHunger` rule is off, survival/adventure
players are kept fed once a second (food + saturation topped, exhaustion zeroed) so they never
starve; when on (the default) vanilla hunger drains normally. (`NuzlockeManager.enforceHunger`.)
- **"PC only at the PC block" now enforced.** A `PCLinkManager` mixin drops remote/portable PC links
(a plain `PCLink` or the predicate overload) when the rule is on, so the PC can only be opened at a
real PC block. The block's own `ProximityPCLink` is never touched — normal PC use (the only way to
store dead Pokémon in a Nuzlocke) keeps working. Blocked remote opens tell the player why. The rule
applies only in Nuzlocke worlds.
### Notes
- Both rules read the per-world value (`NuzlockeManager.effectiveRules()`) when the world is configured,
else the global config — same as the rest of the 0.39.0 options.
## [0.39.0] - 2026-06-13
### Added — NUZLOCKE tab at world creation (all options, visible at last)
- **A real NUZLOCKE tab on the create-world screen**, sitting next to GAME / WORLD / MORE
(`CreateWorldScreenMixin` widens the tab-navigation `Tab[]`). It shows the four gamemode toggles
inline plus an **"All Nuzlocke options…"** button to the full screen. This replaces the old
top-left button that was hidden behind the tab bar — the reason the options weren't visible at
world creation.
- **The setup screen now exposes every option** (15 total) in a two-column layout with sliders for the
integers: Nuzlocke, encounter lock, permadeath, spectator-on-loss, healing mode, starter mode,
**starter level (1–25), shared EXP, EXP-per-KO, EXP boost (1–16×), capture-zone mode, share
same-structure zones, route generation, hunger, PC-only-at-block**.
- Choices made in the tab/screen are **parked per change** and pushed to the integrated server on world
join; they only configure a **new (unconfigured) world**, so loading an existing world never gets its
rules clobbered (`preWorld` flag on the submit payload, gated server-side).
### Changed
- The world-setup payloads now carry the full rule set as **one NBT blob** (`NuzlockeRules`) instead of
a 6-field tuple (`PacketCodec.tuple` maxes at 6) — this is what let the long option tail reach the
world. `NuzlockeData` persists all 15 per world (older saves fall back to the rule defaults).
- The Nuzlocke manager now reads **starter level, EXP boost, capture-zone mode, share-structure zones,
and route generation** through the per-world rules when the world is configured (else the config),
via `NuzlockeManager.effectiveRules()`.
## [0.38.0] - 2026-06-13
### Changed — Routes clear whole trees + English biome names
- **Routes now fell entire trees in the path.** When a route's clearance pass hits a log or leaf, it
flood-fills the connected logs+leaves (26-neighbour BFS, bounded) and removes them all — no floating
canopy left behind, no item drops (`fellTree` in `RouteGenerator.clearAbove`). Previously only the
single block directly above the road was cleared, leaving stumps and hovering foliage.
- **Route names are now `"Route N: <Biome> <Biome>"` in English, derived from the biomes the road
traverses** (e.g. `Route 3: Plains Forest`). Replaces the old Spanish flavour-name pool. The name is
stamped eagerly with the start biome while a directional walk is being laid (`Route 3: Plains`), then
re-stamped with both endpoint biomes — keeping its number — once the walk connects to a node.
Endpoint biomes that match collapse to a single word (`Route 4: Taiga`). `biomeWord`/`biomeSpan`
helpers in `RouteGenerator`; `nextRouteName(descriptor)` / `restampDescriptor` in `RouteData`.
## [0.37.0] - 2026-06-13
### Added — Nuzlocke options (config; world-creation tab is the next step)
- **Starter level** (`nuzlocke_starter_level`, default 5, max 25): a freshly chosen starter is set to
this level on `STARTER_CHOSEN`.
- **EXP boost** (`nuzlocke_exp_boost`, integer, default 1×): multiplies every EXP gain via
`EXPERIENCE_GAINED_EVENT_PRE`.
- **Capture-zone mode** (`nuzlocke_zone_mode`: BIOME / ROUTE / BOTH) + **`nuzlocke_share_structure_zones`**
(same-type structures share one zone vs. one per instance). Biome zones are selectable again; ROUTE
(city/route/structure) stays the default, BOTH adds a biome fallback in open wilderness.
- Flags **`nuzlocke_enable_hunger`** and **`nuzlocke_pc_only_at_block`** (the PC-block lock + hunger
enforcement wiring follows).
### Notes
- These live in the config screen (`/cobbleverserpg config` or key K) for now. Moving them into a
proper **NUZLOCKE tab** in the world-creation screen needs the per-world setup payload to switch from
a 6-field tuple to an NBT blob (`PacketCodec.tuple` maxes at 6) — that refactor is the next step.
## [0.36.0] - 2026-06-13
### Changed
- **Routes seek flat ground instead of charging over mountains.** Directional walks now sample
candidate headings each step and steer toward the **flattest** one (with a small on-course bias),
and the A* climb penalty is much stronger (squared height-change × 5). Roads contour around hills
and hug level terrain — and they still **never terraform**; only the existing surface is paved.
- **Natural, unambiguous route names**: a themed pool ("Sendero del Roble", "Paso del Río", "Vereda
Brumosa"…) replaces "Ruta 1S"-style names, removing the S/6 confusion; names cycle with a numeral
suffix once the pool is exhausted.
## [0.35.0] - 2026-06-13
### Added
- **Capsule Display Pedestal** (Module 5, phase 2): a craftable block (6 quartz blocks + an amethyst
shard) that displays a sealed Pokémon. Right-click it with a **filled** Pokémon Capsule to set the
capsule on the pedestal — it **floats, bobs and spins** above the block (block-entity renderer),
with the sealed Pokémon's **name billboarded** over it when you're close. Right-click empty-handed
to take the capsule back; breaking the pedestal drops it. This is the mod's first custom block
(block + block-entity + renderer infrastructure, in a new `block` package).
## [0.34.0] - 2026-06-13
### Changed
- **Nuzlocke zones are never biome-based.** A position now resolves only to the **city**, **named
route**, or **structure** it belongs to; open wilderness falls back to the **nearest city or route**
(whichever is closer), never to a biome. The only non-city/route/structure key is a single generic
"Wilderness" zone, and only when nothing has been discovered yet. (Legacy biome-keyed records from
old saves still display via their biome name; `/cobbleverserpg nuzlocke reset` for a clean chart.)
- **Directional walks wander more while searching** for a node: a stronger per-segment random drift
plus a slow sinusoidal sway sweeps the walk across directions, so roads cover more ground (and reach
more nodes) instead of ruling near-straight lines.
- **Roads are named the moment they start being laid**, not only when they connect: a directional walk
gets its "Ruta N<dir>" name (from its bearing) as soon as it begins, and carries that name over to
the final city edge when it connects. Abandoned walks release their provisional name (no save leak).
## [0.33.0] - 2026-06-13
### Added
- **Nuzlocke setup from the main menu** (GDD Module 8 — the CreateWorldScreen integration): the vanilla
**Create New World** screen now has a "Nuzlocke Rules (Cobbleverse)" button (top-left). It opens the
rule screen pre-world; the chosen rules are parked client-side (`PendingNuzlockeRules`) and pushed to
the integrated server on join, configuring that world — so you set the Nuzlocke up while creating the
world instead of waiting for the in-world prompt. The in-world first-join prompt and
`/cobbleverserpg nuzlocke setup` still work for already-running worlds.
## [0.32.1] - 2026-06-13
### Fixed
- **Server-tick crash in the route engine** (`NullPointerException` in `RouteData.edgeKey`): a shelved
**directional walk** (whose target is a `walkKey`, so the job's `b` node is null) was requeued as a
fixed `Edge(a, b)` with a null `b`, which then NPE'd in `isCompleted` and took down the tick loop.
Walks now requeue by key into `pendingWalks` and city-edges into the queue (`shelveJob()`), and
`enqueue()` drops any malformed (null-endpoint) edge as a safety net.
## [0.32.0] - 2026-06-12
Sprint from the new testing environment.
### Added
- **Notification style toggle** (`notification_style`, client-side): the mod's pop-ups can be shown
as a corner **toast**, a big on-screen **announcement** (title + subtitle), or a **chat** line —
routed through a new `Notifications` client helper (waypoint discovery uses it).
- **Kanto gyms are road "cities"**: a shipped `#cobbleverse_rpg:gyms` structure tag (Brock, Misty,
Lt. Surge, Erika, Koga, Sabrina, Blaine, Giovanni, Kanto League — optional entries) is added to
`route_city_structure_tags`, so roads connect the gyms. Region gyms also connect via
`connect_all_structures`.
- **Challenged players turn to face the trainer**: when a route trainer's line-of-sight engagement
fires the "!", the player's head snaps toward the NPC (synced to client) to acknowledge the duel.
### Changed
- **No item revives in Nuzlocke**: any item / machine / command that would revive a fainted (0 HP)
Pokémon is rejected in `onHealAttempt`; only a Pokémon's **move** (e.g. Revival Blessing, which is
applied by the battle engine and never hits this out-of-battle gate) can bring a fainted ally back.
- **Healing config reads as "Healing Allowed"** (en) / "Curación Permitida" (es); the `ITEMS_ONLY`
mode no longer implies revives.
- **Game-over is a mass notification**: losing the run now pushes a bold **title + subtitle + sound**
to every player (plus the existing chat broadcast), not just a chat line.
- **One command root**: the client config command moved from `/cobbleverse_rpg` to `/cobbleverserpg`,
unifying with the server's `routes`/`nuzlocke`/`soullock` subcommands.
- **License changed from CC0 to a revenue-share, source-available license** ("if you make money, the
author makes money"): free for personal/non-commercial use; commercial use requires a revenue-share
agreement. `fabric.mod.json` license id updated.
### Datapacks (testing profile) — reverted (see notes)
- **All-region gyms**: enabling the Hoenn/Johto/Sinnoh datapacks **crashes world load** on their own
because they reference **Terralith** biomes (Terralith-DP must be enabled too — that's why all four
sit together in `datapacks/extra/`). They also don't persist: the **Modrinth profile restores
pack files** on launch. To get every region's gyms, enable all four (regions **+** Terralith) via
the world's Data Packs screen — note Terralith overhauls terrain. The mod's `#cobbleverse_rpg:gyms`
tag (above) connects whichever gyms are present, so Kanto works out of the box.
- **Hunger**: the No Hunger datapack is Modrinth-managed and auto-restored; disable it through
Modrinth / the pack to re-enable hunger (a manual file move gets reverted on launch).
### Deferred
- **PC requires the block**: blocking remote PC access safely needs the pack's exact remote-PC
mechanism (likely cobblenav) identified first — a blind Cobblemon `PCLinkManager` mixin risks
breaking all PC access, so it's held until the trigger is confirmed in-game.
## [0.31.0] - 2026-06-12
### Added
- **Pokémon Capsule** (PLAN Módulo 5 "Encapsulación", supersedes the GDD v1 Showcase): craft with
3× glass + 1 amethyst shard. **Sneak + use** the empty capsule on your own sent-out Pokémon to
seal it — it leaves your possession entirely (party/PC), its full data moves into the item, and
the capsule takes the Pokémon's name with an enchant glint: a display piece for item frames.
**Right-click** a filled capsule to release it back (party, PC overflow; capsule consumed).
Tooltip shows species, level and ✝ for deceased memorials. Battling Pokémon can't be sealed;
dead ones can (and return still dead — permadeath holds). Note: an encapsulated Pokémon doesn't
count as "owned" for the game-over check — capsuling your last living Pokémon ends the run.
Placeable pedestal/miniature display is the planned phase 2.
## [0.30.0] - 2026-06-12
### Added
- **Soullock co-op** (GDD Módulo 6 / PLAN Módulo 2) — groups of up to **four players**:
- **`/cobbleverserpg soullock link <player2> [player3] [player4]`** links the parties **slot by
slot** (GDD: "las mismas ranuras"): every slot where ALL players have a living, unlinked
Pokémon becomes one soul group (persisted in `SoullockData`). A purple broadcast announces the
forged link. `unlink` dissolves your links; `status` lists them with partners, owners and ✝.
- **Shared death**: when a linked Pokémon dies (permadeath), every soulmate dies with it —
**even if its owner is offline** (their party/PC storage is loaded on demand); online owners
get the dark-red message and an immediate game-over check, offline ones meet the news on login.
- **No lone combat** (`prevent_lone_combat`, the config existed since 0.2.0 — now it works): a
linked Pokémon refuses to be sent out unless EVERY partner is in its owner's active party with
the owner online.
- Phase 2 (planned): creating links from the trainer's interaction-ring GUI instead of commands.
## [0.29.0] - 2026-06-12
### Added
- **Defeated bodies now lie on the ground.** No render mixin needed after all: Cobblemon's server
delegate skips pose recalculation when `enablePoseTypeRecalculation` is off, and both that flag
and the pose persist to entity NBT — so Defeated Catch freezes recalculation and forces the
**SLEEP pose**, laying the model down for the whole catch window (survives reloads). Species
without a sleep animation fall back to their idle pose gracefully.
- **Aquatic route signage** (the last Module 1 signage gap): a sign that would land on water now
stands on a **cobblestone buoy** at the surface — aquatic channels get their end/junction
signposts, legible from a boat, instead of silently skipping them. (The submarine bed already
paves in gravel/cobble/stone, never sand, since 0.12.0.)
## [0.28.0] - 2026-06-12
### Added
- **No forfeit in Nuzlocke** (`nuzlocke_no_forfeit`, default on) — the project's **first mixin**.
Cobblemon validates every battle choice through `ShowdownActionResponse.isValid` and re-prompts
the player on an invalid one; `ForfeitActionResponse.isValid` was an unconditional `true`. A
10-line mixin turns it `false` while Nuzlocke is active (honouring the per-world setup), so the
server rejects the surrender through Cobblemon's own illegal-choice flow and tells the player
"there is no surrender — fight!". Fleeing WILD battles is untouched (that's the encounter-lock's
flee rule); this blocks giving up against trainers/players.
- Mixin infrastructure established (`cobbleverse_rpg.mixins.json`), unblocking the visual-faint
spike and future deep hooks.
## [0.27.0] - 2026-06-12
### Added
- **Defeated Catch** (PLAN Módulo 6): defeated wild Pokémon no longer despawn instantly — they
stay on the ground for `defeated_catch_seconds` (default 60), **still catchable**, marked by
grey smoke and a "%s is down — 60s to catch it!" action-bar notice for nearby players.
- Implemented by reviving the wild to **1 HP** the moment it faints (a dying entity can't be hit
by Poké Balls — vanilla projectiles ignore health ≤ 0), with AI frozen and silenced; the 1 HP
also gives the natural near-max catch-rate bonus.
- A **failed capture** during the window despawns the body right after the ball breaks free;
an **expired window** poofs it (cloud + extinguish sound). Either way the **Nuzlocke zone
verdict is deferred**: the encounter only counts as FAILED when the window resolves uncaught —
defeating your first encounter now gives you one last minute to secure it.
- Survives restarts (absolute expiry stored in the Pokémon's persistent data, re-registered on
entity load); level-cap and zone-lock ball gates still apply to the downed body.
- The lying-down faint pose still needs the GeckoLib render spike (Phase 5) — for now the body
stands frozen with smoke.
## [0.26.1] - 2026-06-12
### Fixed
- **0.26.0 did not compile**: the level-cap helper methods (`partyLevelCap`/`overLevelCap`) were
referenced but their definitions were lost in an edit anchored on a method removed in 0.13.
Definitions restored; the pre-push verification now flags missing method/variable symbols on the
mod's own classes as real errors instead of dependency noise.
## [0.26.0] - 2026-06-12
Big batch release from in-game feedback.
### Changed — route engine v2 ("conexión por descubrimiento")
- **The locateStructure pairing is GONE** (it was the remaining performance hog). Each node now
launches up to `max_route_connections` (default **3**) **directional walks**: evenly spread
bearings with random jitter that pave forward segment by segment, drifting gently at random, and
**connect to whatever node the terrain they generate reveals** (≤96 blocks of the frontier) —
bending into its cardinal gate without overwriting anything. Walks persist across restarts, are
abandoned after `route_walk_max_length` blocks (default 1600) or at impassable terrain, and the
per-node connection cap applies on BOTH ends — no more cities with a road per direction.
- `connect_all_structures` now defaults **true**: ruins, fossils, shipwrecks, outposts, henges,
gyms — every surface structure is a node ("camino a todo tipo de estructura"). Existing configs
keep their saved value — flip it manually.
- **Vanilla villages no longer generate** (datapack override of `minecraft:villages`): the pack's
bca villages replace them, so roads stop finding duplicate default villages.
- **Spawn is always beside a waystone**: on a world's first boot a Waystones block is planted two
blocks from spawn (once, persisted; inert without the Waystones mod).
### Changed — Nuzlocke
- **No bag items during battle** (`disable_combat_inventory`, finally wired): healing items on a
Pokémon whose owner is mid-battle are rejected.
- **Capture level cap** (`nuzlocke_capture_level_cap`, default on): Pokémon above your strongest
party member's level can't be caught — the ball bounces (out of battle) or the roll is zeroed
(in battle), and the throw never burns the zone. (RCT's own badge-based cap isn't readable
without its API; the party-max proxy is the house-rule equivalent.)
- **Zone header**: the capture-zone notification is now a big centred on-screen title + subtitle
(zone name + ○●✔✘ state) instead of an action-bar line.
- **Setup screen** gained a **starter selection** row (Default / Default+random / Three random),
persisted per world and overriding the global config.
### Changed — route trainers
- **Line of sight 15 blocks** (new default; existing configs keep their saved value).
- **Instant battles**: on sight the trainer engages immediately — no walk-up. Busy players (already
battling) reset the trainer's recognition: it simply retries when they're free and still in view.
- **Gym leaders & Elite 4 exempt**: trainers standing inside ANY structure (gyms, league towers)
never auto-engage — they require direct interaction.
### Deferred (PLAN)
- **Forfeit removal** needs a mixin into Cobblemon's forfeit action (no event exists) — first mixin
of the project, scheduled separately.
- **Xaero chunk-colour zone layer** needs Xaero's WorldMap jar in `libs/` to compile against its
highlighter internals; `nuzlocke map` waypoints remain the interim map view.
- **Tera/Mega/Gmax world toggles** belong to mega_showdown's config — can't be controlled from
another mod without its API.
## [0.25.0] - 2026-06-11
### Added
- **Route trainers duel each other** (`route_trainer_skirmishes`, default on): two idle route
trainers within sight of each other near a player square off — they face each other, close in,
and exchange battle effects (crit/cloud particles + attack sounds) for ~15 s, ending with a
winner's celebration and the loser fuming. Per-pair 5-minute cooldown; a player entering either
trainer's line of sight takes priority (the duel breaks off and the player engagement runs).
These are **ambient skirmishes**: a real NPC-vs-NPC Pokémon battle with their actual teams needs
the trainer mod's API (Cobblemon only exposes player battles — pvp/pve/pvn), noted in the PLAN
for the rival-system work.
## [0.24.0] - 2026-06-11
### Added
- **Route trainers with line-of-sight auto-battle** (GDD "rutas con entrenadores"), implemented
**mod-agnostically by entity id** (no compile-time dependency on rctmod):
- When a road completes, persistent trainer NPCs (`route_trainer_entity`, default
**`rctmod:trainer`**) are placed along it — one per `route_trainer_spacing` blocks (default
160, max 6 per route), jittered beside the centreline, never in water, away from both cities,
once per route ever (persisted). With the trainer mod absent (or a typo'd id) the feature is
completely inert.
- **Line of sight**: every half second, a trainer within `trainer_los_range` blocks (the config
existed since 0.2.0 — now it does something) with a clear sight line to a player does the
classic routine: **"!"** (angry particle + chime), walks up to the player, and on reach
triggers the battle by simulating the player's interaction — the same code path as
right-clicking, so RCT's own battle flow is what runs. Per trainer-player cooldown (60 s),
10 s chase before giving up; spectators/creative and players already in a Pokémon battle are
ignored.
- Note: if RCT's trainer entity id differs in your pack version, run
`/cobbleverserpg routes structures`-style discovery via `/summon` autocomplete or F3 targeting
and set `route_trainer_entity` accordingly.
## [0.23.0] - 2026-06-11
### Added
- **`/cobbleverserpg routes structures`** (op): dumps every structure in the registry — id, tags,
and a `[CITY]` flag for the ones the current `route_city_structure_tags` already match — to
`cobbleverse_rpg_structures.txt` in the game directory. The way to find a gym's real id instead
of guessing.
- **Roads blend with their biomes**: the paving palette now follows the landscape — deserts and
badlands pave in smooth sandstone/sandstone, snowy and frozen biomes in cobblestone/gravel/stone,
swamps and mangroves in packed mud/coarse dirt, and everywhere else keeps the classic dirt-path
mix. Junction/street detection understands all the new palette blocks (primaries like smooth
sandstone and packed mud count as strict road evidence).
- **Subtle perimeter grooming**: a sparse ring just beyond the road's edge (≈35% of cells, 2-block
band) clears small plants only — grass, ferns, flowers — never trees or solid blocks, so the road
reads cleanly without a shaved strip.
## [0.22.0] - 2026-06-11
### Changed
- **Route generation is much cheaper and smoother.**
- At most **one corridor snapshot + A\* every 2 ticks** (was 4 whole segments per tick — the
snapshot is the expensive part), narrower corridor (detour 28→24), and jobs whose frontier is
far from every player are parked instead of burning processing.
- The per-cell structure-avoidance query is **memoized per chunk**: chunks without structure
references (the vast majority) skip it entirely.
- **Better hill handling**: the climb penalty is now applied to the *square* of the height change
(a 3-block scramble costs 9× a gentle step), so roads contour around elevations on smooth
grades instead of charging up them.
- **Smooth direction changes**: a per-45° turn penalty makes paths flow in progressive curves —
no more hard 90° corners or zigzag stitching.
- **Cities now have four cardinal gates** (N/S/E/W points on the standoff ring): roads leave
through the gate facing their destination and aim at the destination's facing gate, so towns
read like classic Pokémon cities with tidy outlets instead of radial spaghetti.
- **Roads never overwrite each other** (nor village streets): paving that meets an existing road
surface adopts those cells and merges into them; the dock connector likewise preserves the
streets it reaches.
### Added
- **Capture-zone identification on the map and HUD**:
- **`/cobbleverserpg nuzlocke map`** (any player) drops one Xaero waypoint per recorded capture
zone — named with its state symbol (● pending, ✔ captured, ✘ lost) and the zone's real name.
Without Xaero the payloads are harmless. Biome zones have no map anchor and are skipped.
- **Zone HUD** (`nuzlocke_zone_hud`, default on): crossing into a different capture zone shows
its name and state in the action bar (○ free / ● pending / ✔ captured / ✘ lost).
- `WaypointDiscoveredPayload` gained a `showToast` flag so bulk waypoint dumps don't spam toasts.
## [0.21.0] - 2026-06-11
### Changed
- **Nuzlocke zones now follow the world's geography** (GDD: "ciudad/estructura/ruta") instead of
being one-per-biome. A position resolves, in priority order, to: the **city** it is in (≤64
blocks of a known city, including waystone cities), the **named route** it is on (≤32 blocks of
a finished road's segment — "Ruta 1E" is a capture zone of its own, like the classic games), the
**structure instance** it is inside (per-instance key, so two ruins are two zones), and only in
open wilderness the legacy **biome** zone. Zone keys are stable per place; messages, the status
command and toasts show the city/route name. Existing biome-keyed records from older versions
remain valid (they simply stop being hit where a city/route/structure now takes precedence) —
use `/cobbleverserpg nuzlocke reset` for a clean chart.
## [0.20.0] - 2026-06-11
### Added
- **Healing sources are chosen in the world setup** (and changeable later): the Nuzlocke setup
screen gained a fifth selector — All / Machines only / Items only / None (hardcore) — persisted
per world and taking precedence over the global `healing_mode` config; `/cobbleverserpg
nuzlocke setup` (op) re-opens the screen to change it at any time. Hardcore-created worlds take
the global config's mode. Worlds configured before 0.20.0 default to All until re-run.
### Changed
- **`PLAN.md` rewritten (v2, Spanish)** around the full feature roadmap: route trainers + RCT
rivals, structure listing, biome blending, submarine signage; zone redefinition
(city/structure/route), 4-player Soullock with interaction-ring GUI; Ditto HM field moves +
Habitat Table; Sophisticated-Storage Ball Storage / Apricorn Processor (2 fases); Pokémon
encapsulation (PC → display ball item); Defeated Catch (1-minute fainted catch window); and the
Action Combat system (research spike). Includes shipped-state per module and a proposed order.
## [0.19.0] - 2026-06-11
Route-quality release addressing the first in-game testing feedback.
### Fixed
- **Severe lag while routes load.** Two causes removed: corridor pre-generation no longer calls
synchronous `getChunk(FULL, true)` on the server thread (100 ms+ stalls per chunk) — it now only
adds short self-expiring chunk tickets and lets the chunk system generate on its worker threads,
resuming the segment when the corridor turns up loaded; and the boot-time `locateStructure`
storm is gone — each city's neighbour search runs **once ever** (persisted `seeded` flag in
`RouteData`) instead of re-running for every known city on every server start, and seeding is
spaced to one call per 2 s (`routes reset` re-seeds everything).
- **Roads paved across treetops and structures.** The pavers and the A* used the `WORLD_SURFACE`
heightmap, which sits on forest canopy and roofs. Heights now come from a ground probe that
descends through logs, leaves and plants (`RoutePathfinder.groundY`), used by the pathfinder and
every placement helper; additionally the A* marks cells **inside any non-city structure**
impassable (`getStructureContaining`, city tags/ids excluded; disabled in connect-all mode), so
roads detour around ruins/towers/foreign villages instead of crossing them. Paved cells also
clear trunks/canopy up to 6 blocks above (roads are walkable under trees).
- **Routes now actually reach the town streets.** The 0.16 connector walked a straight line and
gave up at the first wall. Docking v2 scans up to 28 blocks around the road end for the nearest
*real* street cell (street block + ≥2 street neighbours, own cells excluded) and paves a 3-wide
connector along a mini-A* path to it — steering around houses; the straight walk remains only as
fallback when no street is found.
- **Submarine routes are now lit**: a sea lantern is embedded in the paved channel floor every 11
cells (same cadence as the land lamp posts).
### Added
- **Hardcore = Nuzlocke game mode at world creation**: creating the world in **Hardcore** in the
vanilla create-world screen auto-enables the full Nuzlocke rule set (lock + permadeath +
spectator-on-loss) — the setup screen then never shows. Non-hardcore worlds keep the first-join
setup screen.
- **Trainer gyms (and anything else) can join the network**: entries in
`route_city_structure_tags` now also match exact **structure IDs** (not just tags), and the new
operator command **`/cobbleverserpg routes addcity [name]`** registers the spot you're standing
on as a named city (default "Gym") and queues its road connections — works for gyms from any
mod and even player builds.
## [0.18.0] - 2026-06-11
### Added
- **Escape Rope** (GDD Module 7 / Phase 9 — first custom item): craft **2× string + 1 ender
pearl → 2 ropes** (shapeless), found in the Tools creative tab. Using it teleports you to a
safe, dry spot at the **nearest known town of the route network** — which includes every
activated waystone, since those register as named cities — so it works with or without the
Waystones mod (a deliberate deviation from the GDD's "nearest visited waystone", using our own
persisted data instead of Waystones internals). Overworld only, blocked while in a Pokémon
battle, 10 s cooldown, consumes one rope (not in creative), enderman teleport sounds at both
ends, action-bar feedback with the town's name. Pixel-art texture + model + es/en names shipped.
## [0.17.0] - 2026-06-11
### Added
- **Starter randomizer** (GDD Module 4) — `starter_selection_mode` finally does something:
- `DEFAULT`: Cobblemon's starter screen untouched.
- `DEFAULT_PLUS_RANDOM` (config default): the vanilla categories plus an extra **"Random"**
category with three random species.
- `THREE_RANDOM`: the screen offers a single category of three random species.
- Random picks are **implemented**, **base-stage** species without the `legendary`/`mythical`/
`ultra_beast` labels, at the same level the pack's own starters use. The offer is seeded from
the world seed + player UUID (and the registry is sorted before shuffling), so each player sees
a stable offer in a given world — reopening the screen or restarting never re-rolls, and the
choice always validates against the same list. Implemented by replacing Cobblemon's
`starterHandler` with a subclass that transforms `getStarterList`; selection, locking and the
mandatory-nickname prompt keep working unchanged.
## [0.16.0] - 2026-06-11
### Fixed
- **Diagonal roads no longer look broken.** The paver stamped a 1-D cross-section perpendicular to
the travel direction; on diagonal stretches those are 5 corner-touching diagonal lines, leaving
checkerboard holes and jagged edges. Each centreline cell now stamps a **round, solid brush**
(radius 2.5) — overlapping discs stay gap-free in every direction, with the ragged-edge
randomness applied to the brush's outer ring. (The A* itself was fine: diagonals already cost √2
with an octile heuristic.)
- Aquatic-channel surface markers are placed as **one buoy per side** of the centreline (outside
the brush); with the new ring-shaped brush edge the old per-edge-cell logic would have built
stone rings walling the channel shut.
### Added
- **Roads now dock into the town's own streets.** When a road finishes, each end extends from the
20-block standoff ring toward the city centre with a narrow 3-wide connector until it touches an
existing village street (dirt path, gravel, cobblestone, coarse dirt, or desert villages' smooth
sandstone), stopping early at building walls/roofs (height jump > 4) and skipping water — so
routes visibly merge into the village path grid instead of dying on the outskirts.
## [0.15.0] - 2026-06-11
### Added
- **Cities adopt their waypoint's name**: activating a waystone within 128 blocks of a known city
now renames that city to the waystone's name (previously only ≤64 blocks collapsed into the
node), and the **end signposts of every finished road touching it are rewritten in place** —
signs, completion broadcasts and route-entry toasts all refer to the city by its waypoint name.
Only a waystone with no city nearby becomes a new node of its own.
- Sign placement is now rename-aware in general: a signpost that already stands at the target spot
gets its text rewritten instead of a second sign being stacked on top of it.
### Removed
- **GitHub Actions build workflow** (`.github/workflows/build.yml`): the Fabric-template CI built
with JDK 25, which Gradle 8.10 can't run, so every push failed and spammed e-mail notifications.
Builds happen locally via `.\gradlew build` (see README); CI can return later properly pinned to
JDK 21 if wanted.
Developer information
Projects on Modrinth are automatically available through a Maven repository for use with JVM build tools such as Gradle.
maven { name = "Modrinth"; url = "https://api.modrinth.com/maven" }