Release 0.1.0 - #1
Merged
Merged
Conversation
- 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
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
|
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:
For more information about GitHub Code Scanning, check out the documentation. |
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



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
developtomainin full. Highlights of what ships in 0.1.0:ocean.seed) — pure functions, headlessly tested, shareable between servers. Spawn biome is the one direct aesthetic knob (ocean.spawn-island-biome)./twadmin boatinspect/restore.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