Skip to content

feat(seasons): rotate season worlds with per-season progress - #36

Merged
TheMeinerLP merged 17 commits into
mainfrom
feat/season-lifecycle
Oct 29, 2026
Merged

TheMeinerLP merged 17 commits into
mainfrom
feat/season-lifecycle

Conversation

@TheMeinerLP

Copy link
Copy Markdown
Contributor

Proposed changes

A SkyGrid world only stays exciting for so long. After a few weeks the early players have explored, built and finished every challenge, and someone who joins later lands in a world that is already used up. This change lets the server run in seasons, as proposed in add-season-lifecycle (#28). Every season gets a fresh, seed-generated world. Challenge and task progress count per season, and the old world goes to an archive directory where an external map renderer can pick it up.

An admin describes seasons as files under seasons/, with a start, a duration, a preset and the active challenges. The plugin prepares the next world ahead of time and switches at the boundary. It moves players over and resets each one once on their first entry into the new season. Players hear about the end on fixed dates (7 days, 24 hours, 1 hour, 5 minutes) with the absolute date and a plain list of what is reset and what is kept. There is no countdown pressure in the sidebar. season advance confirm never ends a season earlier than the configured minimum notice; ending at once needs season advance now confirm and is logged with the actor. A postponed end and the notices already sent are stored, so a restart neither forgets a postponement nor repeats a notice.

Every step of the switch is persisted before and after its side effect, so a crash or restart resumes where it stopped. A server without season files behaves exactly as before: one continuous legacy world, no world created and no transition. Existing progress is moved into season legacy by the migrations, with no row lost. I checked that on the live SkyGrid server (in maintenance) with a jar built from this branch. All three database units migrated on the async warm-up after Done, season list reports no seasons, and the tier reset still works.

Known limits, also stated in docs/ADMIN.md:

  • A failed world creation is not retried automatically. Admins get an alert, and season retry resumes it.
  • A missing active world is not regenerated from its seed on startup.
  • Dates are shown in UTC, in the viewer's language.
  • The reset list names only what this change resets. Economy and prestige extend it when they ship.

Type of change

  • fix: bug fix (non breaking)
  • feat: new feature (non breaking)
  • feat!/fix!: breaking change
  • docs: documentation only
  • refactor / chore / ci / test: no user facing change

Checklist

  • Commits follow Conventional Commits (the PR title is used as the squash commit)
  • I added or updated tests where it makes sense (495 tests across api, core, command, config, persistence with Testcontainers, platforms and ArchUnit, written before the code)
  • I updated the documentation where needed (docs/ADMIN.md, Seasons section)
  • ./gradlew build test passes locally (all touched modules with --max-workers=2, plus shadowJar)

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Test results

  249 files    249 suites   2m 30s ⏱️
  522 tests   522 ✅  0 💤 0 ❌
1 566 runs  1 490 ✅ 76 💤 0 ❌

Results for commit b060602.

♻️ This comment has been updated with latest results.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Challenge tiers and task completions are keyed by season, and task
completions also by prestige step (always 0 for now). Existing rows
migrate to season "legacy" at step 0. Progress services resolve the
season through a CurrentSeason port, which returns "legacy" until the
season lifecycle exists.
Adds the season run table in its own Flyway folder and history table,
a SeasonRunRepository port with a SeasonRunConflictException, and the
JPA adapter. A stale save is rejected through @Version. The switch from
one active season to the next is one unit of work, and the partial
unique index keeps a second ACTIVE row out of the table.
The effective end of the active season after a postponement or an advance
and the notices already sent are stored on the season run, so a restart
keeps the moved end and does not send a notice twice. Each decision reads
them back before it runs, so building the service never reads the database.
Wires the season lifecycle into the plugin: a season service behind
CurrentSeason and SeasonAdminUseCase, a Multibinder for transition
listeners, Bukkit adapters for season worlds, the once per season reset
and the announcements, a join listener that routes players into the active
season world and sends the final week line, and a scheduler that ticks the
season service every 600 ticks once the database warm-up is done. The
season run repository joins the async warm-up. Without a seasons directory
the plugin behaves as before: no season is loaded and no transition runs.
…ason

A server that stopped between the switch and the player move restarts
with the season state not yet read, so a joining player was left in the
main world with no reset. The join now waits, checking every 10 ticks,
until the active season and its world are known, then routes and resets
the player once. Quitting while waiting cancels the check.
@TheMeinerLP
TheMeinerLP force-pushed the feat/season-lifecycle branch from 26fdfb8 to b060602 Compare October 28, 2026 20:59
@TheMeinerLP
TheMeinerLP merged commit 2cf55e1 into main Oct 29, 2026
9 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.

1 participant