Skip to content

Release 0.1.0 - #1

Merged
tastybento merged 112 commits into
mainfrom
develop
Aug 8, 2026
Merged

Release 0.1.0#1
tastybento merged 112 commits into
mainfrom
develop

Conversation

@tastybento

Copy link
Copy Markdown
Member

The initial release of TradeWinds: a sea-trading game mode for BentoBox — an endless, procedurally generated ocean of NPC trading islands where the boat IS the cargo hold. Buy low, sell high, smuggle, hunt bounties, claim an island, and answer for all of it at customs.

This PR brings develop to main in full. Highlights of what ships in 0.1.0:

  • The seeded ocean: every island position, type, tech level, security band, biome and name derives from one config seed (ocean.seed) — pure functions, headlessly tested, shareable between servers. Spawn biome is the one direct aesthetic knob (ocean.spawn-island-biome).
  • One Boat, twenty hulls: the virtual hold lives in the boat (Bamboo Raft → Pale Oak Chest Boat), with trade-ins, captures, an always-sells shipwright, cargo expanders as the endgame sink, and a full boat-lifecycle admin logbook plus /twadmin boat inspect/restore.
  • A market worth reading: recipe-derived base prices, island personalities, price drift, whole-coin spread that never closes, the salvage economy, and the resale shelf for notable goods.
  • Travel with teeth: free rowing vs fuel-priced warps, the border-curtain warp dialog, the misjump into the Interstice — now a destination in its own right, with wart shoals, blaze pickets, wither watchtowers, and a ship graveyard with nether-tier loot under the soul flames.
  • The law layer: reputation bands, customs scans, contraband, fines, police that never drop loot, and bounties that push the richest criminals into the dangerous water where hunters wait.
  • Progression: charting, the 19-rank Seafarer ladder, the claimable wild islets (Tide Captain, first come first claimed), member warp nodes, and navigating home by the stars.
  • Quality: 600 unit tests, CI on GitHub Actions and CodeMC, SonarCloud clean (4 remaining flags are deprecated-upstream test constructors awaiting a MockBukkit 26.2 release).

Requires Paper 26.2+, BentoBox 3.18+, Java 25, and Vault with an economy plugin.

🤖 Generated with Claude Code

https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr

tastybento and others added 30 commits July 29, 2026 08:49
- TRADEWINDS_SPEC.md (authoritative spec) and CLAUDE.md
- GameModeAddon with overworld ocean + Nether-environment interstice, no End
- Poseidon-style ocean generator with Stage 1 terrainScale mask hook
- Settings with galaxy/travel/illegal-trade config skeleton
- 22 unit tests green on patched MockBukkit (added worldgen/biome to patcher)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
…ration

- Pure galaxy package (no Bukkit): GalaxyEngine, Elite-style NameGenerator,
  SplitMix64 hashing; constructive min-separation, starter density floor,
  distance-based security bands, per-type biome tables with frozen ice lanes
- Generator terrainLift mask lifts grassy islands from the ocean floor
- BiomeProvider resolves galaxy biome keys; registrar creates unowned named
  BentoBox islands on center-chunk load with band flags and metadata
- 41 tests green

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
…very

- /tw spawn now teleports to the world ocean spawn (no spawn island needed);
  world spawn pinned to (0, sea+1, 0)
- Player command strips create/reset — player islands are purchase-only
- IntersticePortalListener seals both worlds against portal creation and
  teleports (config flag alone did not stop Multiverse)
- /twadmin islands + /twadmin tpisland for island discovery pre-generation
- 46 tests green

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
- Deterministic dock: seeded bearing, generator-terraformed stone-brick quay
  with type-specific plank deck; flattened plaza with blend ring (pure
  geometry in galaxy package; GalaxyConfig gains seaLevel)
- IslandDecorator populator: bell, lantern posts, palette-colored market
  stalls; 3-5 villagers with per-economy professions and per-biome skins;
  iron golems scaled by security band; all persistent and PDC-tagged
- 55 tests green

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
- Dock strip now takes precedence over the plaza blend ring, so the quay
  runs unbroken from plaza edge to pier end (was cut ~10 blocks offshore by
  the ring blending to submerged terrain); regression test walks the dock
  axis on 12 islands
- Villagers spawn with 1 trade XP to lock their profession (zero-XP villagers
  were reset to unemployed or claimed stall barrels and turned fisherman)
- 56 tests green

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
…ights

- Signature landmark per island type (INDUSTRIAL smoking chimney, MINING
  shaft head, AGRICULTURAL wheat plot, FISHING smokehouse + moored boat,
  FOREST log pile, LUXURY fountain, FROZEN ice beacon)
- Plaza surface material per type; type-colored banner + lantern at pier end;
  profession workstations beside stalls
- galaxy.type-weights config (GalaxyConfig fallback to enum defaults)
- 58 tests green

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
- Paper 26.2 dialog API verified and used for the warp dialog (multiAction
  buttons with customClick callbacks)
- RouteGraph: seeded Euclidean costs, per-edge overrides, arrival on the
  origin bearing just inside the destination border
- TWPlayerData/PlayerDataManager: persistent charts, starter cluster
  pre-charted; ChartingListener charts islands on entering their range
- FuelService: hold-only fuel (chest boat + bundles), cheapest-first burn,
  lava bucket leaves its bucket
- WarpService: cancellable TWWarpEvent, dismount/teleport/re-seat, warp
  sickness effects; BorderPromptListener auto-offers at borders;
  /tw warp and /tw chart
- 75 tests green

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
- bands.* config: per-band monster spawning, PvP, hurt-villagers rank;
  applied at registration, /twadmin reflag for existing islands
- Residents: mobs never target them, only players can hurt them, tether
  teleports strays home, killed residents respawn after a delay
- /tw settings restored (visitors get a read-only view of the island they
  stand on; ops edit via /twadmin settings)
- Navigation boss bar: island | standing | dock distance, band-colored,
  fills as you approach the pier
- Starter kit: trading bundle + owned boat on first spawn, seated on water;
  carried boats auto-launch at spawn
- MockBukkit patcher: added damage_type registry diff (sulfur_cube_hot)
- 84 tests green

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
The protection edge (400) is the border players see - enter/leave messages
and the Border wall live there. The warp offer ring now straddles it (was at
the range edge ~1000, 550 blocks past the perceived border) and warp arrival
lands just inside it (~350 from center) instead of far out at ~950.
84 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
Replaces margin-inside-border with a direct travel.warp.arrival-distance
(default 130, inside default view distance) - playtest found 350 a boring,
disorienting paddle. 84 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
CREEPER_DAMAGE=false + CREEPER_GRIEFING=true asserted each enable for both
worlds - the GRIEFING flag must stay true or core cancels whole explosions
for non-members (everyone, on unowned trading islands). Residents were
already immune to non-player damage. 84 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
- PriceModel: produce/demand factors, band-scaled margins, stock drift with
  persisted per-island TWIslandData and hourly decay
- BlueBook base prices via reflection (soft dep) over a config fallback table
- HoldService: chest boat + expander shulkers + max 3 bundles; pockets never
  count; trade dialogs (sell/buy/shipwright) transact hold-only via Vault
- Cargo expanders: purchase-only lore shulkers, doubling price, capped
- TWTradeEvent for the Stage 6 contraband hook; item drop/pickup visitor-
  allowed on trading islands; starter kit deposits starting balance
- 100 tests green

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
- Every market screen shows balance and approximate hold space; sell/buy
  pages get Back, main menu gets Close via the dialog exitAction slot
- economy.PriceEngine ports BlueBook's logic in-addon: base-prices table +
  recursive recipe derivation (cycle-safe, smelting fuel share, output
  division), durability and enchantment weights; reflection bridge and
  softdepend removed; injectable recipe source, fully tested
- 109 tests green

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
Scrolling is easy to miss (kids, accessibility). Default and shipped config
20 -> 8; live test-server config updated. 109 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
Vanilla respawn entombed players in the seabed (0,42,0 death loop). The
galaxy engine now raises a small plains islet at 0,0 (generator-made,
galaxy.spawn-islet-radius, galaxy islands unaffected), world spawn pins to
its surface, and SpawnRespawnListener sends bed-less deaths in TradeWinds
worlds there. Existing worlds: delete the four origin region files.
114 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
Dialog buttons cannot distinguish click type, so each cargo row is a
three-button grid: x1 | x16 | All (sell) and x1 | x16 | x64 (buy), via
multiAction columns(3). MarketService.sell takes an amount cap. Sell button
hidden when nothing sellable; sell rows capped at 8 with a truncation note.
114 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
- /tw chart (boated): caller-only TextDisplay holograms glide out to a
  10-block ring on each charted island's true bearing, stacking nearest-
  lowest on shared bearings; configurable fade; /tw chart list keeps text
- /tw starchart: shared contextual MapView, holder always centered, band-
  colored island dots with names, off-map islands pinned to the edge;
  map id persisted in TWWorldData and renderer re-attached after restart
- 119 tests green

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
economy.starter-coal (default 8 = 64 fuel units) goes inside the trading
bundle, which is hold space - warp-usable from the starter rowboat. 119
tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
saleCatalog appends CHARCOAL when a type's catalog has no fuel-valued goods;
charcoal reclassified as WOOD (cheap at FOREST, distinct from mined COAL).
Only the skint and fuel-less row. 121 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
- Customs stamp on all trader-bought goods (PDC + lore, optional glint);
  traders buy stamped goods only, with configured unstamped exceptions
  (SUGAR) while illegal trade is enabled; hold accounting predicate-aware
- Outfitter shelf at every market: bread always, charcoal fuel fallback,
  per-type gear (smiths arm you at INDUSTRIAL, beds at farms, rods at
  fisheries); prices recipe-derived; fits the dialog (tested)
- Wild islets seeded into empty galaxy cells (deterministic, vanilla
  biomes); all protection flags default-allowed outside named islands -
  open ocean and wild islets are free Minecraft country
- /tw restart: confirmable, capped career reset keeping the chart
- Spec gains 5.0 The two economies
- 126 tests green

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
Shipwright page gathers the cargo progression: Oak Boat ($20) and Oak Chest
Boat ($60, explicit base prices above plank cost - crafting from wild-islet
timber stays the frugal path) delivered to the player inventory, plus the
cargo-expander ladder moved in from the main menu. 127 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
Undeclared permissions default to op-only in Bukkit - non-op players were
locked out of every command added since Stage 0 (warp, chart, starchart,
trade, restart, settings). All player commands now default true; admin
commands op.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
isFixIslandCenter/isEnforceEqualRanges -> false (arbitrary centers and
ranges - also protects our jittered trading islands from grid realignment
and clears the path for Stage 7 claims). The spawn islet becomes a real
unowned BentoBox spawn island: protection 100 (config), range 200 (provably
overlap-free with starter islands), named Spawn, set as world spawn island.
128 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
Re-asserted every enable so existing spawn islands update: BOAT,
HURT_MONSTERS and CRAFTING at visitor rank; MONSTER_NATURAL_SPAWN,
TNT_DAMAGE and BLOCK_EXPLODE_DAMAGE off. 128 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
New players see where to go the moment they set sail - no telling needed.
Config chart.show-on-boarding; holograms clear on quit. 128 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
/tw spawn and any other teleport bring the hull and chest-boat contents
into the inventory (overflow drops at the destination). The warp is
untouched (it dismounts first); shared boats are never confiscated.
133 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
/tw spawn already uses DelayedTeleportCommand - it needs only
commands.delay.time > 0 in BentoBox config (ops bypass). The warp fires from
a dialog callback, so WarpService gained travel.warp.stand-still-seconds
(default 0): moving during the countdown aborts the jump and salvages the
fuel as charcoal. 133 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
Both bypassed the physical loop: warp from the dock instead of rowing to the
border, trade without walking to a trader. The intended paths (border
proximity prompt, right-clicking a plaza trader) remain open to everyone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
Principle 2 (risk symmetry) was half-built: rowing was free AND safe.
- Interstice: warps roll failure, stranding player+boat in the Nether sea
  partway along the route with Ghasts; the owed destination is remembered
  and re-engaged FREE via a repeating dialog (fallback to nearest charted
  island, so stranding is impossible). WarpService.deliver() split out.
- Encounters: band- and distance-scaled rolls spawn day/night-appropriate
  mobs ahead of rowers - guardians, trident drowned, ZOMBIE_NAUTILUS,
  phantoms, pillager pirate crews and a sea witch in boats - with
  customs-stamped booty, making combat the third income stream.
- 138 tests green

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
Reserve the origin cell for a full SAFE trading island named Spawn (economy
via galaxy.spawn-island-type, FISHING default) - dock, plaza, market,
villagers, boss bar and warp zone all come from the existing systems, and
other islands are pushed clear so min separation holds. It is registered and
designated BentoBox's spawn island (setSpawn + spawn point on the plaza), so
/tw spawn lands players in a working port and admins can rename/move/reflag
it normally. The bare spawn islet is gone. 140 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
tastybento and others added 27 commits August 3, 2026 19:21
Right-clicking a trader at spawn threw ClassCastException: Integer cannot be
cast to Double, inside PriceEngine.getPrice.

My fault from Phase 2. When I regenerated the base-prices block I wrote
"WHEAT: 20" rather than "20.0", and YAML types a number by how it is written.
BentoBox's deserializer promotes Integer to Long but not to Double, so the
Integer went straight into a Map<String, Double> - where generic erasure means
nothing complains until the first read, at the trader, mid-dialog.

Fixed at both levels. The shipped config now writes decimals, but that alone
would be a trap waiting for the first admin to hand-edit a price as "20", so
Settings also coerces every Map<String, Double> through asDoubles() on the way
in - base prices, fuel values, crime bounties and edge overrides. The wildcard
parameter type on that helper is load-bearing and commented as such: declaring
it Map<String, Double> would make the compiler insert the very cast that
throws.

Three tests: the crash reproduced through the setter with Integer, Long and
junk values; every numeric map covered; and a guard that the shipped config
writes prices as decimals.

No database wipe needed - the coercion reads the existing server config fine.

295 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
Two playtest reports.

An oak boat carried 30 slots because the shipped config.yml had every value in
boats.ranks multiplied by ten - cargo slot counts caught up in the whole-coin
money migration. Slots are not money. BentoBox replaces map settings from config
rather than merging them, so the wrong values beat the correct code defaults,
and since every unit test reads the code default the build stayed green.

That is the second time config/code drift has shipped a broken game this way,
after base-prices shipping 27 of 112 entries. So the real fix is not the data:
ConfigAgreementTest now walks every @ConfigEntry map in Settings by reflection
and asserts config.yml agrees with the code default, key for key and value for
value, comparing numbers by value so 20 and 20.0 are the same setting. I
corrupted two entries deliberately to confirm it names both rather than passing
vacuously.

Second, with NBT preserved the sell page showed three identical "Iron Sword"
rows at different prices and nothing said which was the enchanted one. Paper's
dialog API can render item icons - DialogBody.item with a real hover tooltip -
but only in the body, never in a button label. So the sell page and the
secondhand shelf now show one icon per row in the same order as the buttons,
tooltips on, and the buttons carry a short marker instead: a star for enchanted,
"(worn)" for damaged, or the item's given name where it has one.

297 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
Three reports from the same screenshot.

The sell page duplicated every good: an icon row above, three buttons below.
Icon-beside-buttons is not achievable - Paper renders items only in a dialog
body, never in a button label, and the two are separate regions - so selling is
now two pages. The list gives one row per distinct good; picking one shows that
good's icon at 32px with its real hover tooltip, the live unit price, the
remaining depth, and 1 / 16 / all. Selling reopens the item page rather than the
list, so the price and depth visibly move as the port fills up, which is the
whole reason to sell in increments.

A button read "tradewinds.market.item-enchanted x1" because itemLabel asked for
that key while the dialog helper prefixes tradewinds.ui. - a missing key does not
throw, BentoBox renders the key itself. That is the third locale failure of this
shape, after key names that YAML read as booleans and a duplicate that was
silently dropped, so LocaleKeyTest now scans every source file for locale keys
and asserts each resolves to a leaf string. It strips comments and @ConfigEntry
paths, since both quote keys that are not lookups, skips concatenated keys, and
asserts the four runtime-built page keys explicitly rather than passing over them
in silence. Confirmed by renaming a key and watching it fail. A uiText() helper
now shares the prefix with ui() so the two cannot drift apart again.

299 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
Delegating display to the server economy's formatter was my call, and it loses
to reality: TradeWinds rounds every price to a whole coin precisely so dense
dialogs read clean, then handed the number to a formatter that put the cents
back and added a currency name on top of the locale's own dollar sign.

Money.format now renders whole coins with thousands separators and one symbol,
configurable as economy.currency-symbol for servers whose money is gems or
credits. All 23 hand-written dollar signs came out of the locale, so the symbol
lives in exactly one place - after checking that every money variable in every
locale string is produced by Money.format, and that item counts sharing the
[amount] name were not caught by the strip. Vault still keeps the accounts;
only display changed.

MoneyTest pins the format, including that a stray fraction rounds away rather
than resurfacing as cents, and that a null symbol falls back to $ rather than
printing "null27".

302 tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
Two faults behind one screenshot. The real bug: bought coal carries the
purchase mark, the mark makes it fail isSimilar against a plain stack, and
moveCargoToFuel matched with new ItemStack(material) - so the transfer moved
nothing, silently, and the player got the one-way message from the withdraw
path instead. Fixed with a stack-matching overload; the mark evaporates in the
tank, because fuel is material-keyed and explicitly exempt from the one-way
rule. Regression test drives the real HoldManager with a marked mock.

The gesture work: left-click picks a stack up onto the cursor, and dropping it
does what dropping means - on the fuel row it fuels (right-click feeds one at
a time, like a furnace), in your own inventory it goes ashore (bought cargo
refused with the reason), anywhere else in the window it goes back. Shift-click
keeps the quick route and right-click still selects for the TNT.

What makes this safe rather than hairy: the pickup is cosmetic. Nothing leaves
the database until the drop lands, so closing with a full cursor loses nothing.
The three routes by which the cursor copy could have become a real item are
each closed - window clicks were already cancelled, drags are refused entirely
while cargo rides the cursor, and on close the cursor is cleared before the
server hands the copy back. Render hides the held amount so a picked-up stack
never shows twice.

302 tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
Phase 6 added two commands and never declared their permissions in addon.yml,
so they had no default and permission plugins denied them - on exactly the
command the harbour report's purchase message tells the player to run.

This is the third member of the artifact-drift family, after the config maps
that BentoBox replaces rather than merges and the locale keys that render as
themselves when missing. Same shape every time: two artifacts must agree, and
disagreement breaks the game instead of the build. Same cure, too:
PermissionDeclarationTest scans every setPermission() call in the source and
asserts the prefixed node is declared in addon.yml with a default and a
description. Verified by re-removing the prices declaration and watching the
test name it.

One trap inside the guard itself, recorded in PROGRESS: permission names
contain dots, which YamlConfiguration reads as nesting, so the permissions
section's top-level keys are path segments rather than permissions. A node
counts as a declaration iff it carries default or description.

306 tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
Ben's call after playtest: not required for MVP. economy.price-logbook-enabled
(default false) now gates all four surfaces - the /tw prices command is not
registered, market visits do not write the logbook, the broker button does not
appear, and buyMarketReport refuses as a backstop. Depth-at-the-counter stays;
it works and is unrelated to the logbook.

The playtest evidence is written down where the revisit will find it, in the
salvage plan's status note: every port read "best: Gems $850" because the
unfiltered view compares absolute prices and diamonds carry the highest base
price everywhere, so the priciest good wins at every port - best must be
priced relative to book. The category argument has no tab-complete and nothing
says what the categories are. And a $500 report purchase produces one chat
line - no reach shown before paying, no list of covered ports after.

One Java footnote: inside the anonymous DefaultPlayerCommand, getSettings()
resolves to the command's own method, so the flag check needs
TradeWinds.this.getSettings().

306 tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
Clocks spin in every API-created world on 26.2 - TradeWinds and BSkyBlock
alike - and tick in the main world, so it is Paper's plugin-worlds-as-custom-
dimensions rework, not addon code. Written down with the evidence and the
compass/bed diagnostic so nobody re-investigates it from our side.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
Stage 7a - Seafarer ranks and islet claiming:
- Rank ladder from charted-island count (config thresholds, locale names),
  charted leaderboard with placeholders, /tw rank, /twadmin rank (stores a
  chartedBonus adjustment; promotion, demotion and reset all work).
- /tw claim registers a wild islet as a real BentoBox island: rank gate
  (default Tide Captain) + price (default 150,000), protection sized to the
  islet, no paste, money moves only after the grid accepts.
- Trading islands' range boxes shrunk to the protection range (and re-gridded
  on adoption): createIsland's full-distance box had reserved ~1.1km around
  every port, where no claim could ever fit.

Stage 7b - living with a base:
- Member-only warp node: the claim travels as a synthetic HomePort spec,
  pinned first in members' warp dialogs, warp-out at its protection border,
  interstice re-engage stores a "home" marker (a wild cell would re-resolve
  to the wrong island).
- /tw go is the one "go" verb: door into the ocean from outside, step home
  on your own island, course-home toggle at sea (8-point by day, exact
  distance by night). /tw sethome + /tw home work only aboard your island.
- /tw unclaim (owner-only, team emptied first, hardDeleteIsland - blocks
  stay), gold HOME markers on chart holograms and the Star Chart, ship's
  compass at FISHING outfitters (lodestone-bound, untracked).
- Respawn: island members keep BentoBox's island respawn (our spawn-safety
  listener was stomping it); the loaner boat is granted a tick after
  respawn (items given during PlayerRespawnEvent are wiped).

Interstice resources (tradewinds-interstice-plan.md, built same day):
- IntersticeMap pure geometry: wart shoals (soul-sand domes, crown proud of
  the sea), wither watchtowers; strandings dodge both via a column test.
- Decorator: wart crops, crimson/warped groves on grand shoals, glowstone
  ceiling clusters, quartz in shoal cores and brazier roots, blaze-spawner
  pickets, watchtowers with wither skeleton spawner + bastion-treasure
  chest. Netherite loot-only; End goods trade-only; entry stays by-accident.
- Lighting a portal frame prints the lore refusal instead of failing silent.

Boats and shops:
- The yard ALWAYS sells: every hull the tech can build except the size you
  sail. Bigger + ship at the quay = trade-in (cargo stays); anything else
  is bought outright with a capture-style confirmation and the old boat
  left unowned where it lies. Ends the rich-but-stranded-at-spawn corner.
- Moored refit fixed by identity: findPlaced() scans loaded entities for
  the PDC stamp instead of a position box (dismounted boats drift), used by
  refit, relabel and the boatIsHere purchase gate.
- Outfitter batch buttons name their good ("Compass x16"), the empty
  slipway notice moved from a truncating button into the dialog body.

World and encounters:
- Deepslate below Y0 (vanilla ore variants come free with decoration).
- Pirate crews actually ride their boat: abandon-ship is per encounter type
  in config; the old flat 30 was inside the 28-block spawn distance, so
  every crew ejected on its first tick.
- PUFFER_SHOAL: the SAFE bands' nuisance encounter - stings, no booty
  (nuisance types are farm-proof by classification), capped at POLICED via
  the new maxBand so it never dilutes real danger.

348 tests green. Placement knobs are needsReset: fresh worlds recommended,
interstice world mandatory for the new features.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
A playtest death 4,400 blocks from home turned into region-file
archaeology: the chart pointed the OLD BOAT marker at an empty dock for
two days while the whole death kit sat frozen in an unloaded chunk,
with TWO copies of the hull item in it. Four causes, four fixes, all
pinned by BoatArchaeologyTest:

- Death drops re-remember every stamped hull at the wreck site (manual
  drops always did; death bypasses PlayerDropItemEvent). No TTL starts:
  death drops freeze in unloaded chunks, and a wall-clock expiry would
  delete the cargo record while the item was still recoverable.
- Creative placement no longer duplicates the hull (vanilla keeps the
  placed item in creative); pickup dissolves a ground copy of a hull
  whose id you already carry.
- The always-sells purchase sheds a carried old hull at the quay via
  BoatService.shedCarriedHull, shared with takeBoat - it removes every
  copy (dedupe) and drops exactly one, under the same swap-quiet.
- Encounter boats are scenery: boarding never captures, breaking
  splinters them - no drop, no farmable hull, no litter record per
  pirate attack.

New: the boat logbook (boats.logbook, default ON) - one console INFO
line per lifecycle transition with id, owner and coordinates - and
/twadmin boat <player> [restore] to inspect records straight from the
database and regenerate a lost active boat as a stamped item, refusing
while a loaded avatar exists.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
… quiet exit

Built across one long playtest evening with Ben, iterating on screenshots:

- Ship graveyard: seeded vanilla shipwreck templates (IntersticeMap.Wreck,
  all 20 variants, rotation/burial/grade from the cell hash) placed by the
  decorator through Structure.place into the LimitedRegion, centre-anchored
  so 28-block hulls survive chunk borders. Each wreck perches on its own
  reef mound raised in the terrain itself (wreckSurfaceAt, crest 6-8 under),
  riding mostly submerged with its top works just breaking the surface.
  Density tuned live: one hull per ~130 blocks (grid 64, chance 0.25).
- Loot mirrors the vanilla nether by grade - fortress chests on common
  wrecks, bastion finds on rare ones, bastion treasure plus a piglin-
  bartering captain's locker on the 5% - but only wreck-loot-chance (0.3)
  of wrecks are stocked at all; the rest are scenery with explicitly
  emptied chests (no vanilla buried-treasure maps in an ocean that cannot
  honour them). Grave-lights: a soul flame on every hull's highest timber,
  mast-carried on the sunken ones.
- The interstice map now seeds off the GALAXY seed, not the world's random
  one: regens are reproducible, coordinates computable offline, and two
  servers sharing a seed share an interstice (spec principle 5).
- The floor stopped being a snooker table: deeper restless seabed profile
  (rifts, seamounts), a new short-wavelength dune field in Seabed
  (SeabedConfig.duneHeight, 32-block lattice; 0 by default so pre-dune
  worlds' floors do not shift - SeabedTest pins byte-identity), and a
  seafloor-clutter pass (glow lichen, basalt spikes, magma vents,
  soul-sand seeps, boulders).
- Blaze pickets grew from a 3x3 crow's nest to a 9x9 deck covering the
  spawner's full spawn radius - blazes used to drop straight into the sea.
- The re-engage dialog shows ONCE per stranding; /tw go and /tw warp raise
  it again on demand, and a quiet action-bar line replaces the repeats
  (prompt-seconds now paces the reminder; 0 silences it).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
All internal design documents (spec, plans, progress log, manual test plan)
now live in docs/, which is git-ignored: the public repo carries the code,
CLAUDE.md points at the docs' new local paths. The files remain in the
working copy only. Note: everything up to this commit is still in history -
if the repo goes public with history intact, the old copies are visible
there.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
CodeMC build #1: 151 test errors, all "No value for minecraft:sulfur_caves
/ sulfur_cube_hot" cascading into "Already mocking". The shared Jenkins
agent's local repo held a STALE copy of the locally patched MockBukkit
under the same version - built before the 26.2 registry fixes - and no
public repository hosts the artifact, so the cache won silently.

The patched jar (1.9 MB) now lives in libs/ as a file-based Maven
repository declared in the pom, and the version is bumped to
4.113.4-p262-2 so no cache anywhere can shadow it. Fresh clones and CI
now resolve the exact jar the suite was written against.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
Seven parallel agents over disjoint file sets, zero-behavior-change rules,
then one integration pass. The mechanical bulk: duplicated literals hoisted
to constants, redundant casts dropped (Math.floorMod(long,int) already
returns int), unused code removed, dangling Javadoc reattached, restricted
identifiers renamed (ReputationService.record -> recordCrime), and twenty
cognitive-complexity refactors done as pure method extraction - worldgen
extractions preserve Random draw order, so seeded worlds are byte-identical.

The two real bugs (S2184): int arithmetic widened too late in
GalaxyEngine.rollBand and StarChartRenderer's text positioning.

The find of the day: Settings.java can NEVER hold static constants -
BentoBox's YAML loader PropertyDescriptors every declared field and throws
on any without a getter/setter, so the S1192 fix there booted the addon
DISABLED with null settings. TradeWindsTest caught it (the only test that
loads Settings through the real YAML path). Reverted to literals; the trap
is now documented in the class and in CLAUDE.md.

Deliberately not fixed (mark won't-fix on SonarCloud): gameplay-random
S2245s, the /twadmin warpfail "debug feature", the GameModeAddon wiring
hub S6539, EncounterType's data-table enum ctor, BentoBox-compat
deprecations, and Settings' literals per the loader trap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
The GushBlock workflow shape, not SonarCloud's wizard suggestion: JDK 25
(Paper 26.2 bytecode is class version 69 - a JDK 17 compiler cannot read
it), sonar coordinates from the pom, analysis gated on SONAR_TOKEN and
never failing the build, and no MockBukkit prep step - the patched jar
ships in libs/, so a bare checkout builds green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
The first token-authenticated Actions run swapped SonarCloud's automatic
analysis for the real Maven scanner - test sources and bytecode-aware
rules included - so the count ROSE after the sweep (114 -> 167) while
nothing regressed: the lens widened.

Fixed: all four S3516 blockers (multi-identical-return commands collapsed
to one return); nine provably-dead conditions (null checks against
@nonnull BentoBox/Paper APIs); a real rare crash - Math.abs(hashCode())%n
is negative at Integer.MIN_VALUE, now floorMod, in the resale-shelf pick;
setArmorContents(null) -> new ItemStack[4]; the customs Optional carried
as a value past its null gate; ~20 test classes of assertion/import/mock
hygiene; and the S3878 array-creation sites - dropped where the call has
one overload, replaced with a shared NO_VARS constant where
getTranslationAsComponent needs the disambiguation.

One lesson banked: a "provably dead" null check in boatId was shielding
bare Mockito mocks, not production - the check stayed deleted and the
mock now stubs a PersistentDataContainer, because production code is
entitled to lean on the API contract.

Remaining opens are deliberate and listed in docs/PROGRESS.md for
won't-fix marking: playSound's nullable-annotated-but-never-null Sound
constants, getLocation()-as-argument artifacts, deprecated event ctors in
tests, MapPalette.matchColor.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
materialOf now uses Material.matchMaterial with the oak fallback instead
of valueOf-and-catch - same behavior for every input, no exception
control flow, and the deep-analysis NPE model goes quiet. Plus the last
duplicated "[name]" in TradeDialog and one inline mock hoisted in
BoatPickupListenerTest. Everything now open on SonarCloud is a
documented won't-fix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
One more duplicated "[name]" in TradeDialog's shelf title, and two prose
comments reworded so the commented-out-code heuristic stops reading
coordinates and class names as statements. What remains open on
SonarCloud is now exactly the documented won't-fix list.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
The right answer to the Settings S1192s that neither the constant sweep
(loader-fatal) nor the revert (duplicated literals) found: the repeated
map keys ARE SecurityBand names, so write them as
SecurityBand.POLICED.name(). No string duplication, no static field for
the YAML loader to choke on, and renaming a band becomes a compile error
instead of a silent config orphan. Ruled with Ben 2026-08-07 while
discussing why the constants had been backed out.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
Ben's brief: listeners, commands and player-interaction classes were
light, and the tests exist to surface latent bugs at the edges and to
protect future change. Eight agent runs (six squads, two replacements
for false green claims, one fixer round), merged and verified here.

New suites: HoldGui (the cursor-cannot-dupe, bought-cargo-cannot-leave
and fuel-by-mouse rules, asserted against the real HoldManager),
MarketService + TradeDialog decision logic (whole-coin spread, hold-only
trading, all three buyBoat branches), the crime core (reputation bands,
fines to Clean and never Upstanding, scan scaling, kill/provocation
paths), commands (claim gates, admin rank/boat flows, the four-way
/tw go branch), boats (enter/destroy/join, One Boat crafting), tasks,
encounters, and SeaArrival.

Line coverage on the named classes: TWClaimCommand 19->96, AdminRank
14->80, ReputationService 12->76, CrimeListener 7->71, StarterKit
12->65, TWSpawn 20->58, AdminBoat 8->57, BoatListener 34->53,
BoatCraft 5->44, TWPrices 0->43, MarketService 29->42, HoldGui 4->26.

Every latent-bug suspect raised was investigated and none survived:
the tech-gate "bug" probed with a trade good (exempt by design - the
test now documents the exemptions), the provocation guard verified
correct by trace, the rest were mock gaps refuted by existing green
tests. The 21 @disabled tests carry honest harness-limitation labels,
not bug claims. Five vacuous test files (never instantiated the classes
they were named for) were deleted rather than merged - coverage that
does not move means the tests test nothing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
The 5,510 new test lines brought 143 code smells of their own - the
same families the main-code sweep already cleared. Two cleanup passes:
51 unused imports, 22 hoisted inline mocks, 20+ dead commented blocks,
unused variables and fields, static imports, and the ten
assertion-free tests strengthened to assert what their names promise
(never deleted, never assertTrue(true)). Full suite unchanged:
598 tests, 0 failures, 21 labelled skips.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
Six leftover imports, an unused field and variable, and the two
@disabled placeholder tests that asserted nothing - deleted with a
comment saying where their coverage belongs, since a disabled test
with no assertions guards nothing. 596 tests, 0 failures, 19 skips.
The four deprecated-upstream constructor calls remain by choice.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
Ben: the space-game word was a leftover from the TradeWars/Elite lineage
and reads as confusing in a sailing game. Renamed in one pass while
nothing is released and no compatibility shim is owed: package
world.bentobox.tradewinds.galaxy -> .ocean; GalaxyEngine, GalaxyConfig,
GalaxyIslandRegistrar -> Ocean*; getGalaxyEngine/getGalaxySeed and the
Settings fields -> ocean*; config section galaxy: -> ocean: (the live
test-server config migrated in place, so seed 20260729 and the world it
generates are unchanged); locale and CLAUDE.md. The startup line now
reads "TradeWinds ocean seed". 596 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
/tw restart deleted the BoatHold record and handed a fresh starter kit,
but left the carried hull ITEM in the pack (and any moored entity in
the world) - a zombie stamped hull whose record was gone, which
re-registered as a fresh free boat on first touch. Result: two boats
in the inventory after every restart, one of them farmable.

New BoatService.strikeAvatars: every carried item with the id leaves
the pack, a loaded placed avatar is removed, the logbook records the
strike; THEN the record dies. An avatar asleep in an unloaded chunk
wakes as ownerless salvage - the cargo died with the record, which is
the career reset working as intended. Regression test pinned.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
Everything downstream of the seed stays a pure function - but spawn is
every player's first sight of the game, so ocean.spawn-island-biome lets
an admin choose that one biome outright (empty keeps the seeded roll,
byte-identical to before; the pre-knob OceanConfig constructor is kept
so existing callers and tests are untouched). The biome also drives the
ground palette, so a cherry-grove port and a desert port look the part.
Unknown keys log once and fall back to the default biome instead of
breaking generation, and the biome provider declares the override so
Paper accepts it. Regression test pins override, blank-equals-seeded,
and no leakage onto other islands.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
Three fixes for one report ("I keep on dying and respawning at that
location, over and over"):

- The cause: the world spawn sits at plaza + (3.5, 3.5), and the galley
  hearth lands at plaza + angle(bearing + 0.7) x (radius - 4) - for one
  unlucky dock bearing those are the same column, and every islandless
  respawn stood the player in open flame. The 3x3 spawn pad is now
  RESERVED: the galley ring-walks past it and amenity pieces skip it.
- The net: the respawn listener no longer gambles on the point being
  safe - it spirals to the nearest column BentoBox's own isSafeLocation
  accepts (spawn chunks are always loaded, so the check is sync and
  cheap). An admin-moved spawn point gets the same protection.
- The first death: /tw returns a sailor to the exact recorded exit
  position - including one recorded at flight altitude, which became a
  lethal drop on re-entry. Recording now clamps to the surface;
  underwater stays underwater, water is soft.

Existing spawn islands need a regen (or a bucket of water on the
campfire) - the pad reservation only applies to freshly decorated
plazas. Three regression tests pinned; 601 total, all green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
The campfire death-loop survived the first fix on a stale database: the
player still carried an island membership from a regenerated world, so
the respawn took the member branch - which stepped aside for BentoBox
entirely, spiral and all. Members now keep BentoBox's CHOICE of where to
respawn, nudged to the nearest safe column, same as the islandless.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014as2obVi1QJj8CRTSqXBRr
@github-advanced-security

Copy link
Copy Markdown

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

@sonarqubecloud

sonarqubecloud Bot commented Aug 8, 2026

Copy link
Copy Markdown

@tastybento
tastybento merged commit 3d34121 into main Aug 8, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants