Skip to content

Island-scoped biome unlocking as a first-class challenge reward (4.0) #145

Description

@minoneer

Problem

Biome access is gated purely on usb.biome.<key> permission nodes (BiomeCommand, BiomeConfig.getAvailableBiomes). Challenge rewards grant these via PlayerInfo.addPermissions → Vault. Two consequences:

  • Without a permissions plugin, biome rewards silently never apply. Grants park in the player-YAML pending-permissions list forever — and that list is re-appended on every skyworld teleport, so it also grows unboundedly (this doubling affects all permission rewards on Vault-less servers, not just biomes).
  • Biome unlocks are conceptually island progression (the biome is an island attribute, challenge progress is island-shared), yet they're materialized as per-player permission state. That mismatch is what forces re-grants on member join, per-member clears on restart, and the associated race conditions.

Decision

Fold into the 4.0 challenge rework (#119/#128) rather than patching 3.x:

  1. Biome unlocks become island property. The island earns the unlock; any member with the canChangeBiome island permission picks from the island's set. Island create/restart resets unlocks naturally with island state — no per-member clearing policy needed.
  2. Biome rewards become a first-class reward type in the challenge catalog (e.g. rewards.biomes: [jungle, ...]) instead of string-sniffing usb.biome.* out of permission rewards at runtime. reward.permission stays a pure permission grant.
  3. The legacy importer translates existing usb.biome.* permission-reward entries into biome rewards at challenges.yml import time (Add explicit challenges.yml migration pipeline #131) — the special-casing happens once, permanently, with operator-visible logging. usb.biome.* wildcards expand against the configured biome list (warn on the rest).
  4. The permission gate stays as an OR-fallback (hasPermission("usb.biome." + key) || island.hasUnlockedBiome(key)) for all of 4.x: donor/VIP/op access stays expressible via permissions, and legacy player-level grants keep working without a destructive migration of external permissions data.

Open sub-decisions for the 4.0 design review

  • Materialized vs derived storage: store the unlock set on the island, or derive it from the island's completed challenges' biome rewards (+ cache, invalidated on completion/reset)? Derived has no new persistent state, no grant/sync surface, and restart semantics follow challenge reset for free — but couples gating to the challenge subsystem and changes meaning when a challenge's rewards are edited retroactively. Leaning derived, given the catalog rework rebuilds the completion runtime anyway.
  • Default biomes: replace plugin.yml-default semantics with an operator-controlled defaultUnlockedBiomes config (bare biome keys, Registry-validated, warn-and-skip). Decide whether the configured default biome is force-included (guaranteed valid choice) or merely warned about when missing (operators can lock everything; matches the warn-don't-force config philosophy).
  • Grandfathering: whether to additionally seed island stores from the union of current members' legacy usb.biome.* nodes, or rely solely on the OR-fallback aging out.

Implementation checklist items that an earlier attempt got wrong

(from the adversarial review of branch independent-biome-unlocking, commit 65c2dc3 — player-scoped implementation, reviewed and rejected: wrong scope plus ~13 defects)

  • usb.biome.* wildcard rewards must not collapse to a dead literal * entry
  • Locale-safe key normalization (toLowerCase(Locale.ROOT)), and gate on the Registry-matched biome key, not the raw command argument (BiomeCommand already mismatches these for permissions today)
  • getPlayerInfo can return null (maintenance mode) — no main-thread player-file I/O or NPEs in the GUI filter path
  • Don't override existing operator knobs (options.restart.clearPerms, resetChallengesOnCreate) with hard-coded reset policy
  • No destructive removal of usb.biome.* nodes from external permissions plugins — leftovers are harmless under the OR gate, and removal is incomplete by construction (offline players, Player-only hook API)
  • Update docs/src/admin/customization.md (currently: biome access "is controlled by permissions")

Worth keeping from that branch: the dual OR gate, key normalization/dedupe approach, and the config-driven default-unlocked-set concept. The branch itself can be deleted once this issue is the design of record.

Related

#119 (challenge modernization umbrella), #128 (4.0 gameplay changes), #131 (challenges.yml migration pipeline — importer home), #110 (GUI migration), PR #144 (independent plugin.yml node fixes + NPE guard shipped for 3.x).

Metadata

Metadata

Assignees

No one assigned

    Labels

    4.0Targeted for the 4.0 release (challenge system rework)enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions