dmg 14fc0dbde0
Release / release (push) Successful in 5m10s
CI / build (push) Successful in 3m15s
feat(crafting): deliver saved sheets to fixed destinations
2026-09-17 23:30:27 -04:00

Fantasy Stick

Development foundation only. No playable gameplay is registered, and this JAR is not ready for deployment.

Fantasy Stick is the proposed transferable-character RPG plugin for Social Minecraft. Its approved design uses Paper character sheets, native equipment storage and a book/basic-stick/seven-ability hotbar. These remain planned native features. The server-independent progression core now tests initial stats/points, stat purchases, CON's equal maximum/current-health gain, capped XP and MAX presentation. YAML persistence, backend death/revival transitions and a native signed-book factory are also present. None is yet wired into playable crafting, health, session or spell interactions.

Build and verify

  • Java 25 toolchain (Gradle can resolve it), Gradle wrapper 9.6.1.
  • Purpur API/runtime 26.2.build.2618-stable; plugin.yml declares API 26.2.
  • Node.js 20 or later and npm for the TypeScript release-tool tests.
./gradlew clean check jar
./gradlew check jar -PreleaseVersion=0.1.0

check runs Java packaging tests, TypeScript release-client/credential-helper tests, and a native compatibility test against the checksum-verified Purpur runtime. Native preparation downloads and patches the declared runtime and loads vanilla registries/components without starting a listening server or creating a gameplay world. The item tests decode signed written books with Minecraft's real codec, without a plugin instance or YAML lookup. A clean build repeats that preparation. Dependencies and runtime downloads require network access.

Build output is build/libs/purpur-fantasy-stick-<version>.jar. Server APIs, tests, credentials, and Node tooling are not bundled in the plugin. Runtime identity is FantasyStick, entrypoint games.dmg.fantasystick.FantasyStickPlugin, data folder FantasyStick, namespace fantasystick. Bootstrap registers no commands, permissions, recipes, sessions or gameplay listeners.

Character storage and keepsakes

YamlCharacterRepository stores schema-version-1 records at <plugin data directory>/characters/<character UUID>.yml. Character identity, name, progress, seven-slot loadout, ability cooldowns, explicit dead flag and latest death ID/time are preserved; the record has no holder-owned equipment or ordinary-health/nutrition snapshot. Integer values and decimal health retain exact precision, and timestamps use UTC RFC 3339 strings. Unknown learned ability IDs and unlearned cooldowns survive reload.

The file adapter uses safe YAML construction, duplicate-key rejection, bounded reads (1 MiB per record), typed validation, UUID filenames and no symlinked record reads. Replacement writes a complete temporary file, flushes it and atomically renames it; expected revisions and transient OS file locks reject duplicate/stale writers. Empty .lock files are coordination files, not administrator-unlock flags. Unsupported atomic replacement fails rather than falling back to a partial overwrite. Malformed files are retained, not reset.

CharacterStorage queues disk operations on its persistence worker and acknowledges successful writes only after commit. Its backend restoration checks the saved dead flag, matching current death ID and death age strictly under 24 hours at the request/selection time. It preserves progress/cooldowns and restores full character health; old books do not become eligible after a later death. This method is not a complete Revive spell: the engine must still validate the caster and native dialog, spend its cooldown, recheck item possession and perform physical conversion safely.

TailBooks creates vanilla WRITTEN_BOOK items with static pages. The full character name remains in display/pages; the native title is bounded to vanilla's 32 UTF-16 units without splitting a surrogate pair. fantasystick:tail_version, character_id and death_id identify the saved record. Pages/title are never read as authoritative stats or revival eligibility. Plugin absence does not remove the vanilla written-book contents. The factory does not yet deliver books from a death event.

File-level atomic replacement is not atomicity with Minecraft inventory/player saves. This checkpoint does not implement cross-save reconciliation, an administrator lock, automatic overflow drops or player/session persistence.

Craft preparation and conservative cleanup

CharacterStorage.prepareCraft accepts an immutable batch of distinct fresh character records and returns a process-local CraftPreparation handle. Allocation/configured starting points remain the caller's responsibility. Retain this handle until delivery or abandonment: ready() returns an observation copy, and cancelling/timing out that future does not cancel the owned operation. Use abandon() to invalidate the attempt, including while writes are queued/in progress. Writes acknowledge the complete prepared batch; claimForDelivery() exposes it once, immediately before native delivery. Claiming permanently rules out cleanup, even if later delivery throws.

Abandonment skips remaining queued writes and schedules cleanup for acknowledged, never-issued creations. Cleanup checks the original file identity/creation and modification attributes and exact bytes under the repository lock. Changed, replaced, corrupt, symlinked, busy or unidentifiable files are retained. Partial-save failure cleans only acknowledged creations and reports uncertain targets without deleting them. Shutdown rejection preserves records rather than inventing successful cleanup. Repeated cleanup cannot delete a later file. Restart never reconstructs cleanup authority from revision zero or scans records for automatic deletion/reissue.

Thirteen craft-persistence tests cover these backend boundaries, including worker timing, partial/post-rename failures, ownership changes, restart, shutdown, claim/abandon races, and mutated/cancelled observer futures. This is not native inventory delivery: the controller still must contain vanilla consumption, validate current view/lifecycle/content and commit actual items. Filesystem locks coordinate cooperating writers; concurrent external filesystem mutation must be excluded/coordinated. File flush/atomic rename does not establish directory power-loss durability or Minecraft/plugin-save atomicity. All create/update/cleanup mutations now share one fail-fast characters/.records.lock coordination file, so abandoning new characters does not accumulate one lock file per ID. Read-only atomic-file loads remain unlocked. The file stays present but its OS lock is held only for an operation, not an administrator-unlock flag. Never unlink it while writers may use it. Old per-ID lock files are left intact; no new ones are created.

Upgrade boundary: stop all older per-ID-protocol writers before opening the same data directory with this shared-lock implementation. Mixing versions or hot-reloading while old worker writes remain queued is unsafe. YAML format and record filenames are unchanged; no deployment or existing-server data migration has been performed. Contention between cooperating repository instances fails without mutation and can be retried, rather than blocking a server tick or deleting coordination files.

Paper sheet and recipe definition

CharacterSheets renders one Paper sheet from a saved live record, with its current name and the exact offhand instruction. fantasystick:sheet_version (integer 1) and character_id (UUID string) identify the character, with no owner lock or duplicated progress snapshot. Identification is only a reference: it does not authorize entry or replace saved-state/session validation. Rendering from a dead record is refused.

CharacterFactory allocates a fresh UUID and randomized two-part character name when explicitly called. It reuses the progression initializer for zero stats, level 1/zero XP, configured unspent points and full base health, with empty abilities/loadout/cooldowns and no prior death. Name collisions are allowed; identity is independent. Construction and recipe previews do not invoke allocation. Unit tests check fresh defaults/configured arbitrary-precision points, request-time allocation, seeded name variation and same-name/different-ID characters. A native composition test prepares a planned batch through YAML and verifies individually named sheets/IDs after native serialization without consuming or delivering inventory items. The actual craft-event controller is still absent.

The fantasystick:character_sheet shapeless recipe definition contains one stick, paper, feather and ink sac. Character creation is manual player crafting only (inventory grid or crafting table). ManualCraftingGuard cancels this recipe's automated CrafterCraftEvent before native consumption/ejection, without touching other recipes or reversing cancellations; register the guard with the eventual completed-crafting adapter before exposing the recipe. Its preview has no character metadata and allocates no identity/name. It is deliberately not registered yet: actual completion, bulk crafting, cancellation/save handling and anvil rename persistence still need their event adapters. Presentation edits preserving metadata are tested, not a claim that complete anvil transactions work.

Manual craft planning (not delivery)

ManualCraftPlan reads real crafting inventories without mutation. It calculates one result for cursor/number/offhand clicks, distinct empty destination slots for shift-click, and single/bulk intent for Q/Ctrl-Q. It bounds quantity by ingredients and storage capacity, preserves native reverse storage order, rejects cancelled/non-craft/wrong-recipe/malformed input, and snapshots full item data rather than retaining mutable native stack mirrors. Returned leftovers are detached copies; matches detects changed inputs, result, recipe, cursor or planned destinations without overwriting intervening items.

This is not a transaction commit. Ten native planner/composition tests use actual 2×2/3×3 crafting containers, player storage, menu-carried stacks and recipe holders, with the absent player/network boundary stubbed. They do not execute packet dispatch or vanilla result consumption. A controller still needs one-use lifecycle tokens (a native menu may be reused after close/reopen), wiring of name/ID allocation and asynchronous persistence, reentrant matrix-update handling and delivery. Drop intent is not proof that a cancellable native drop succeeds. No recipe/listener registration, recovery policy or cross-save atomicity is added by this planner.

Fixed-destination craft delivery (not registered)

ManualCraftDelivery can apply a validated plan and exact-sized saved batch to the cursor, selected hotbar/offhand slot or bulk storage destinations. It rechecks current contents before claiming delivery, constructs all sheets first, then debits fresh native input mirrors and places all outputs before triggering one matrix recomputation. This deliberately relies on the pinned Purpur 2618 mirror/count/setter behavior. No further adapter-planned writes occur after the prepare-callback boundary. Existing native setters may send packets; this is not packet-silent or exception/crash-atomic.

Nine native delivery tests cover normal/right-click, bulk and exact-slot delivery, partial ingredient metadata, empty first matrix cells, stale context refusal, mismatched/abandoned/reused batches, reentrancy, callback item changes, new-menu cursor safety and post-callback exceptions. The fixture uses actual containers/cursor stacks and intercepts slotsChanged; it does not execute a listening server's recipe manager or event dispatch. A mismatch refuses before consuming cleanup authority; an exception after claiming delivery conservatively retains records rather than replaying or rolling back.

The caller still must contain vanilla consumption, validate a one-use lifecycle/attempt token, schedule on the server thread, pass the player's actual current view and synchronize the appropriate client menu afterward. World-drop plans return UNSUPPORTED_ROUTE unchanged in this component; that is not a registered gameplay policy. No completed-craft listener or recipe is exposed yet.

Native equipment adapter

EquipmentStorage preflights and moves nine hotbar/four armor slots into an actual named shulker in one free main-inventory slot. Item counts, metadata/damage/enchantments and legal nested contents remain native item data, accessible without this plugin. Eligibility is tested against the real vanilla shulker slot for every nonlegacy native item material; a main-inventory shulker does not prevent entry. Oversized or ineligible equipment and missing space produce unchanged refusals.

Version-1 box metadata uses fantasystick:storage_version, storage_owner_id, storage_session_id and storage_slots. Box positions 0–8 contain original hotbar slots 0–8; positions 9–12 contain boots, leggings, chest and head from player slots 36–39. These metadata fields are for identity/restoration, not the only copy of the items. Restoration searches main slots 9–35, so sorting relocation does not lose the box. Repeated requests, wrong identity, duplicate/malformed boxes, unexpected extra contents or occupied restoration slots do not overwrite/delete items. These are adapter error results, not a persistent administrator lock or completed recovery policy.

The caller must remove authenticated fantasy controls before restoration. One new head stack uses the slot freed by removing the box, even with a full main inventory; there is no drop fallback. The adapter does not yet route sheet/cursor events, emit entry warnings, manage character/player snapshots or register a playable session. Native tests use real player-slot storage with only the absent network recipient stubbed; client packets and live gameplay remain unverified.

CI and publication

Gitea CI verifies pushes/PRs; main pushes run semantic-release and can publish immutable versioned JARs. Source checkout is anonymous from the public repository; external action revisions are pinned to full commit SHAs. CI and actual release publication must both be verified for the exact source commit before any deployment preparation. The operator has provisioned RELEASE_TOKEN and the bootstrap is pushed. Do not infer successful CI, publication or credential validity from the secret name alone; record actual exact-commit workflow and artifact results in the canonical wiki.

The release credential must be a repository-write credential authorized for dmg/purpur-fantasy-stick. Provision it through the approved protected-file/stdin workflow; never paste it into chat, commit it, or supply it as a command argument.

  • Git receives the credential through git-credential.sh using Git's pipe-based credential protocol, restricted to HTTPS, git.garvis.dev, this exact repository path and username dmg. The helper ignores store/erase requests and never writes the token. Recognized semantic-release token environment names are unset so they cannot become credential-bearing Git URLs. HTTP redirects are disabled. This works even when semantic-release overrides GIT_ASKPASS.
  • API upload uses an in-process TypeScript client, a fixed repository URL, timeouts, redirect refusal and sanitized failures. Credentials are read from the release step's environment, never arguments or generated request files.
  • Publication creates a draft, uploads/checks the expected asset name and size, then publishes. Failed calls are not automatically retried, existing releases/assets are not overwritten, and raw errors/response bodies are not logged.
  • A failed run may leave a tag or draft. Inspect server-side state before proposing an approved recovery; rerunning does not imply a new release and must not overwrite an immutable asset. Verify downloaded bytes/checksum and embedded JAR version separately after publication.
  • Release-client tests use synthetic credentials and stubbed HTTP responses. They do not prove a real token works or a release exists.

Canonical knowledge and delivery

Stories, design, open decisions and evidence are in the private SoMC wiki project. See AGENTS.md for the shared process. Source builds do not need wiki access.

No inventory recovery guarantees, gameplay balance, multiplayer behavior, or production compatibility have been verified by this bootstrap. Production rollout and GitOps changes remain separately approved; the development cycle never pushes GitOps.

S
Description
Transferable RPG characters for SoMC on Purpur; implementation in progress.
Readme
4.8 MiB
0 Stars 1 Watchers 0 Forks
v1.163.0
Latest
2026-10-17 15:46:10 +00:00
Languages
Java 99.8%
TypeScript 0.2%