dmg af7c75b6ed
Release / release (push) Successful in 15m37s
feat(parties): audit native protection decisions
2026-09-25 14:11:48 -04:00

Fantasy Stick

Transferable characters, equipment restoration, inventory safety, character inspection, native session health, durable downed decisions and combat Revive, parties, and switch delay have automated implementation coverage. The full RPG remains in development and is not deployment-ready.

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. Crafting, native-health offhand sessions, read-only character books and book-managed parties with native damage protection are assembled; active food overrides, stat/ability book actions, combat progression and spells remain incomplete. 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. Saved-sheet crafting and accepted anvil-name persistence are wired. Stat purchases and spell execution remain incomplete.

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.
  • ProtocolLib 5.4.0 is required at runtime and declared as a compile-only dependency; it is not bundled. Install the official server distribution, not the Maven library JAR (which lacks bundled runtime dependencies).
./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. Startup registers the manual crafting controller and automated-Crafter guard before the character-sheet recipe. Equipment sessions and permission-gated administration are registered; spell execution remains absent.

Local event files

Fantasy Stick independently writes compact CloudEvents 1.0 JSON objects as UTF-8 JSONL under <plugin data directory>/events/YYYY-MM-DD/events-<boot UUID>-<segment>.jsonl. The initial foundation records plugin lifecycle/recovery and correlated observed/resolved native interactions for active characters: block/entity/item/inventory/vehicle/control use, item pickup/drop, held-slot changes and discrete teleports. The downed feature additionally records durable downing/resolution facts, learned combat-Revive casts and results, accepted final-death/Tail transitions, prevented waiting/protection damage, protection expiry, and authorized downed-policy reads/changes. Stable episode/action identities and original deadlines, attribution, custody, locations, frozen rewards, caster cooldown/range/line-of-sight context survive recovery. Phoenix Down purchase, explicit downed-menu use, exact item/health proof, protection/prevention and expiry records are emitted only from their acknowledged feature boundaries. It intentionally excludes movement/look ticks, rendering, particles and unrelated ordinary-player activity. It has no dependency on Event Producer, no network endpoint and no player-facing log reader.

A stable plugin-owned server UUID and per-lifetime boot/sequence identity distinguish records. Events rotate at UTC midnight, on each writer lifetime and before 128 MiB; an oversized record stays whole in its own segment. Accepted work is first retained in internal pending files and exported at least once with unchanged IDs, so consumers must deduplicate by (source, id). Startup recovers pending work, quarantines malformed/interrupted tails outside *.jsonl, and reports incomplete prior lifetimes without inventing a shutdown. Queue overload and pre-durable native-observation crashes remain truthful loss windows; logging never rolls back or replays gameplay.

These files have no automatic deletion, compression or retention limit, so disk use grows until operators manage it under a separately approved policy. Current generic records include stable account/character/session IDs and precise locations; later feature-owned event slices may include complete public and recognized private-message text. Restrict event files and backups to operators accordingly. They are not encrypted or cryptographically tamper-evident, and feedback/particle preferences do not disable auditing.

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 equipment coordinator now intercepts active-session native death, restores gear and normal health, attempts the entry-location return, and saves authoritative death before guarded one-book conversion. Stale/offline/uncertain delivery is reported without reissue; this is not the complete Revive spell. Native health/session integration is described below.

File-level atomic replacement is not atomicity with Minecraft inventory/player saves. Player/session receipts and proof-based equipment reconciliation are implemented below, not an administrator lock, automatic overflow drops or guaranteed cross-save recovery.

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 backend alone is not native inventory delivery: the assembled controller separately contains vanilla consumption, validates current view/lifecycle/content and commits 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 three separate lore lines: Character sheet:, Place in off hand, to enter fantasy world. Recipe previews and acknowledged crafted sheets use the same layout; existing items are not bulk-rewritten. 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 assembled craft-event controller now invokes this allocator only for accepted current attempts.

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. Startup installs this guard and the manual controller before exposing the recipe. Its preview has no character metadata and allocates no identity/name. Manual completion, bulk crafting, cancellation/save handling and accepted anvil-name persistence are registered. 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. Eleven 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. The assembled controller separately supplies one-use lifecycle tokens (a native menu may be reused after close/reopen), allocation/persistence wiring, reentrant 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

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. The registered controller uses this component only for fixed destinations and routes Q/Ctrl-Q separately.

Q/Ctrl-Q craft delivery

Q/Ctrl-Q remain supported scope; no shortcut restriction was accepted or enabled. ManualDropDelivery executes saved drop plans one unit at a time, checking original contents minus completed debits before every next unit. PlayerSheetDrops creates an unspawned item, dispatches PlayerDropItemEvent, revalidates, and uses native Item.spawnAt for the cancellable ItemSpawnEvent path. It never uses inventory-source dropping or cancellation's return-to-inventory fallback. A successful unit consumes one ingredient set, then recomputes; cancellation stops the suffix without charging it.

Thirteen additional tests cover sequential/bulk/cancelled drops, exact remaining-input validation, stale context, changed/removed entities, same-batch reentry and exceptions. Tests combine real native matrices, serialized sheets, YAML and Paper's event dispatcher; world insertion itself remains an explicit API boundary double, supplemented by pinned-runtime bytecode inspection. No live world or packet dispatch is claimed.

After public callbacks, records are conservatively retained—including unused batch records. Repeated late cancellations can therefore accumulate records. A spawn callback can change inventory after an output escapes but before a safe debit; UNCONFIRMED means stop/report without speculative charging, deletion, rollback or replay. This is not arbitrary-plugin/cross-save atomicity. The controller must hold a per-attempt guard across callbacks (including against newly started attempts), revalidate actual current view/lifecycle, and synchronize the client. The registered controller supplies these guards and has native lifecycle coverage as described below.

Manual craft controller

ManualCraftingController contains vanilla handling before deferred work, allocates/saves accepted plans, returns delivery to an always-deferred server-thread callback and synchronizes the player's actual current inventory afterward. It uses attempt-object identity plus a delivery reservation that survives close/reopen and remains held through client-sync callbacks. It invalidates on close, accepted open, quit and disable; stale callbacks cannot clear a newer attempt, and rejected scheduling abandons owned unissued records without worker-side player access.

Twelve native controller tests cover saved deferred delivery, Q/Ctrl-Q routing with full storage, replay/reentrancy, menu reuse, client-sync reentry, pre-existing cancellation/unrelated recipes, rejected intents, allocation/save/scheduler failures, postclaim exceptions and retained-file reporting. Review reproduced a planning exception escaping before vanilla cancellation; capture now always cancels in finally. Async diagnostics are collected and asserted on the test thread rather than lost inside discarded futures. These tests invoke controller callbacks with real native grids/items/YAML and explicit player/scheduler/world boundaries, not a listening server. The controller checkpoint passed all 153 tests, audit and static analysis. The newer registration assembly is described below.

Scheduler injection must always enqueue (never run inline) or throw on rejection. Diagnostic callbacks must be nonthrowing, thread-safe and avoid native player access. The existing cancellation boolean cannot distinguish this handler's containment from a later plugin's independent veto; prior cancellations are preserved. No new Q/Ctrl-Q restriction, global inventory lock or automatic uncertain-delivery replay is introduced.

Startup, configuration and shutdown

The packaged FantasyStickPlugin now wires storage, allocation, deferred scheduling, native drop-event dispatch and logger diagnostics. Both protective listeners are installed before fantasystick:character_sheet is exposed. Recipe-key collisions and partial registration failures clean up this enable attempt without replacing an existing recipe. Shutdown invalidates attempts before closing storage, unregisters its listeners and removes its recipe; removal exceptions are reported while remaining cleanup continues.

An optional <plugin data>/config.yml setting controls new characters' initial points:

starting-points: 40

The default is 40; nonnegative arbitrary-precision integers are supported. Invalid values fail before gameplay registration. Existing saved characters are not rewritten. Configuration is read on enable; no reload command or automatic data migration is supplied.

Nine native lifecycle tests load the actual packaged entrypoint through a test configured classloader and native JavaPlugin.setEnabled, then register/dispatch through Paper. They cover registration ordering, configured points, all craft routes, distinct persisted sheets, installed Crafter cancellation, failed startup and shutdown/queued-callback cleanup. Server registry/scheduler/player/world insertion are explicit test ports; this is not a listening world/client test. The assembled checkpoint passes clean/incremental packaging, all 162 tests, audit and static analysis; remote publication is tracked separately. This historical crafting checkpoint predates the equipment-session integration below; the remaining RPG is not complete.

Accepted anvil names

CharacterAnvilNames observes Purpur's non-cancellable accepted-take event, not anvil previews. Matching input/result character identities and changed displayed text are captured on the server thread; CharacterStorage.renameCharacter then changes only name/revision on the persistence worker against the latest live record. It preserves stats, XP, levels, points, health, loadout, cooldowns and historical death identity. Same-name requests do not rewrite; missing/dead records cannot be created or revived by renamed items. Clearing the custom name uses the native item-factory name, including an explicit item-name component where present.

Vanilla retains responsibility for item movement, XP charging and anvil damage. The inspected event fires at the start of native accepted-take processing, before that continuation; the observer does not make native completion contingent on YAML acknowledgment. Failure/crash may leave item presentation ahead of the saved name. The original saved progress is retained and captured IDs are logged; there is no automatic item/XP rollback, reissue or guaranteed retry.

Six backend tests, seven native observer tests and an added packaged-entrypoint registration test cover preservation, sequencing, failures, shutdown, preview/take separation, name fallbacks and later serialized sheet/Tail rendering. Native XP/damage execution and live clients are not exercised by these fixtures. Active overhead/chat/tab presentation now refreshes after acknowledged saves, including renaming a copied reference held by someone else; stale session/revision callbacks cannot rename a replacement holder. This slice passes 176 tests, clean/incremental packaging, three additional native-suite repeats, audit and static analysis. A controller test's leftover retry was explicitly drained before fixture reuse (12 focused stress runs pass); production shutdown remains nonblocking. Remote publication is tracked separately.

Native health ceiling (historical core checkpoint)

CharacterProgress now limits base maximum health to min(10 + CON, supplied native limit) without limiting CON purchases. A purchase adds only the actual maximum-health gain to current health: fractional gain when crossing the ceiling, zero gain once capped. Injury and revision/idempotency protections remain intact. The two-argument initializer uses the pinned runtime's default 1,024 HP; runtime adapters must pass their actual ceiling explicitly.

NativeHealthLimit.read() reads the current native ranged attribute through the pinned runtime's public Craft/native accessors, without setting attributes or changing configuration. It must run on the server thread after registry initialization. Incompatible accessors or nonpositive/nonfinite/unrepresentable native ranges fail explicitly instead of guessing. This is not a refusal based on CON. Tests cover core purchases and actual native attribute compatibility at 10.5, 1,024 and 2,048 HP; native tests restore their temporary in-process range changes in finally.

At this historical checkpoint, no virtual health, global configuration change, stored-data migration or active-player health adapter was introduced. The persisted schema's raw 10 + CON validation envelope remains unchanged for compatibility. The later native session integration is described below; a complete Revive spell remains unfinished. Current-health gain is calculated against the core instance's fixed supplied limit; callers must obtain/revalidate the applicable limit at their native operation boundary. This slice passes 186 tests, clean/incremental packaging, audit and static analysis. Remote publication is tracked separately.

Session claims

SessionClaims supplies process-local exclusion for pending/active character sessions. A single shared registry atomically prevents simultaneous claims for either the same character identity or the same player's active mode. This does not limit how many sheets a player may own or permanently bind a character to a holder. Released characters can transfer; a stale token cannot release a replacement claim. Private claim objects carry distinct session UUIDs for future equipment/callback correlation. Dead/zero-health DTOs refuse unchanged; shutdown invalidates all claims and refuses new ones.

Six unit tests cover exclusion, transfer/switch release, stale callbacks, invalid input, shutdown/restart boundaries, same-name identities, uncapped values and 24 rounds of 16 competing callbacks. The registry does no persistence or native work and does not impose a native-health cap. It must be shared across entry routes; separate processes do not coordinate. Callers still must load current authoritative state, validate sheet possession/context/deadlines, preflight native equipment/health, retain/release claims across their lifecycle and reconcile interrupted state. isCurrent() is a lifetime check, not saved-state or effect authorization. The equipment coordinator now uses one shared registry for its entry/exit routes. The historical foundation passed 182 tests, clean/incremental packaging, audit and static analysis; remote publication is tracked separately.

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 coordinator supplies authenticated control ownership to the adapter, which validates all destinations before replacing controls. One new head stack uses the slot freed by removing the box, even with a full main inventory; there is no drop fallback. The equipment coordinator/event bridge now routes sheet/cursor events, emits entry warnings and registers sessions. Native health/vitals integration is described below; active nutrition overrides remain unfinished. Standalone display projection is implemented below. Native tests use real player-slot storage with only the absent network recipient stubbed; client packets and live gameplay remain unverified.

Equipment sessions and safe box movement

The historical US-002 completion checkpoint passed all acceptance criteria and 359 automated tests (315 Java/native and 44 release-tool tests), clean/incremental packaging, dependency audit and static analysis. The new equipment path loads authoritative characters, claims player/character identity, and acknowledges <plugin data>/sessions/<player UUID>.yml before packing. Preparation failure leaves gear unchanged. Nine typed bound controls occupy the hotbar; book/ability actions were not yet implemented at that checkpoint. Current inspection and party actions are described below; basic/learned attacks remain unfinished.

The genuine Fantasy Storage Box contains actual original items. Picking it up for a move leaves it in main storage and issues a harmless bound cursor move-control. Confirming a valid main-storage destination exchanges the real box and that destination's contents; closing the inventory cancels the move. Sorting, stale callbacks and lifecycle changes cannot confer movement authority on a copied control. Full storage needs no extra space or overflow drops. Ordinary sheet movements, including Q/Ctrl-Q, remain supported.

Exit removes controls and restores original slots once, locating the genuine box by owner/session rather than original index. A new head uses the freed box slot. Native player phase markers and the persisted receipt must agree for recovery; ambiguous state is preserved/reported, never guessed into replacement gear. Shutdown is best-effort and nonblocking; accepted cleanup drains before its receipt worker closes. Equipment stays vanilla-readable without this plugin.

Optional character-swap-cooldown-seconds defaults to 900; zero bypasses the player-bound restriction. Existing absolute deadlines survive configuration changes; only a new actual exit starts a new deadline, and repeated cleanup does not extend it. The switch-delay setting is read on enable; there is no general reload command or deployed configuration change.

Administration (permission fantasystick.admin, default operators):

  • /fstickadmin character find <name...> and character show <character-uuid> — asynchronously locate authoritative saved characters and inspect one exact UUID read-only.
  • /fstickadmin character view <online-player> — opens the exact online account's current active character as a complete read-only virtual book for an in-game administrator.
  • /fstickadmin character recover <character-uuid> <online-player> — previews and explicitly confirms a generation-revoking reissue of the same saved character; dead characters use a distinct no-Tail revival action, and unresolved native delivery remains fail-closed.
  • /fstickadmin inspect <player-uuid> — asynchronous receipt/native-session inspection.
  • /fstickadmin recover <player-uuid> — validated online equipment/game-mode restoration only; no forced overwrite, progression grant or ambiguous replay.
  • /fstickadmin config adventure-mode [true|false] — show or persist the policy for new sessions; no change to active sessions.

Tests use actual native items, inventories, serialization, Paper event dispatch and menu-close behavior, with explicit scheduler/player/world ports. Drag continuation ordering includes a model based on pinned bytecode. The packaged lifecycle fixture isolates the entrypoint but delegates helpers parent-first. Live clients, production PluginClassLoader execution and arbitrary-plugin/crash atomicity are not claimed verified. No deployment or existing-data migration is authorized by story completion.

Standalone character presentation

Saved character identity/progress transfers between holders; ordinary equipment, vitals, return location and switch deadline do not. Native reference/session tests cover live/dead/invalid/copied identities, concurrent exclusion, orderly switches and legitimate backend revival. The complete Revive spell remains unfinished.

ProtocolLib-backed presentation shows the full literal saved name above the player, in chat and in the player list, without changing the server account name/UUID/profile, scoreboard membership, ordinary display/list fields or authoritative name-tag-distance attribute. A detached client-only TextDisplay replaces the native overhead label; display-name-only player-info packets and the chat renderer's sender-name argument cover the other surfaces. No signed message-body or recipient rewrite is introduced.

Output resources belong to a player connection/session, not the sheet. Exit/defeat/lifecycle cleanup withdraws them independently of successful gear restoration, using current ordinary server state rather than replaying stale fantasy names. Renames refresh only after authoritative file acknowledgment; future sessions also reload saved names. Reconnect/restart discards client-only labels, not ordinary identity data.

Cross-plugin display compatibility is unsupported, as explicitly requested. There is no Stealth/Leaf/Ignis bridge or nickname/concealment guarantee. Native/platform visibility and ordinary cancellations are covered; supplemental presentation-owned packets bypass ProtocolLib filters to avoid recursive projection. Names can therefore conflict with other plugins' presentation or packet-only concealment. This does not disable ordinary protection or inventory safeguards.

Tests exercise actual native codecs, renderer inputs and ProtocolLib dispatch with explicit transport/server/player ports. They do not prove live-client appearance or client delivery acknowledgments. Unknown transport outcomes are reported without speculative spawn replay; cleanup remains best-effort. Label reconciliation is tick-driven; live-client/network-load observation remains a follow-up. Optional Adventure mode is described below; its verification does not complete the remaining fantasy health, nutrition or combat features.

Optional Adventure mode

adventure-mode: true is the default. A new entry captures this policy; enabled entries save the holder's original game mode in the player/session receipt before changing it to Adventure. Disabled entries leave the mode untouched, as do holders already in Adventure. This state never transfers with the character.

The admin command saves on a worker, preserving unrelated configuration values, before publishing its effective value. Invalid values and failed writes leave that value unchanged. Already active sessions keep their captured policy. Accepted writes drain without blocking shutdown; immediate re-enable defers startup asynchronously until those writes finish, then reads the saved policy.

Exit, defeat and best-effort lifecycle/recovery cleanup restore only an owned Adventure change. Observed later admin changes relinquish ownership, including an observed change away and back. Native cancellation refuses entry without packing; a cancelled restoration retains its receipt for inspection/retry. Interrupted or mismatched player markers are not replay permission. Mode cleanup is independent of uncertain equipment cleanup.

A same-process retry can acknowledge gear already verified restored for that exact player while retrying a vetoed mode restoration; a serialized RESTORED marker alone cannot establish this after restart. Missing game-mode proof leaves the mode and receipt retained even when independently verified gear can be restored and synchronized. Player-data and YAML saves are not one transaction; unobserved out-of-band state edits are not detectable history. No forced player save, blanket Survival reset or deployment is introduced.

Native fantasy health, exit and defeat

Offhand entry applies actual native maximum health min(10 + CON, native health limit), resumes saved remaining character health, and supplies the recurring blue active-character aura to native viewers who opt in with /fstick particles players on. The account-owned aura preference defaults to off; /fstick particles players reports it and /fstick particles players off hides only this recurring aura. The independent, default-on /fstick particles all [on|off] preference durably controls every explicit Fantasy Stick particle for that account without changing nearby players' delivery, sounds or gameplay. Turning all off temporarily takes precedence without overwriting the saved players choice. A fresh character's saved health starts full. CON has no gameplay cap; high values do not reject entry. Native damage/healing rules remain in effect, with the existing party/mixed-mode protection. The book reports current native health rather than waiting for an exit save.

NativeMaximumHealth owns only the effective-value cache/dirty callback of that player's existing native maximum-health instance. Base values, modifiers and native potion expiry remain owned by their original sources; nothing is copied back from an old modifier snapshot. The native player health methods and client attribute snapshot are tested. This adapter uses pinned Purpur 2618 internals, including private fields; a detached startup probe exercises installation, recalculation and removal before gameplay is registered. Runtime upgrades require renewed native compatibility verification, not a guessed fallback or global attribute-limit changes. Matching native health scaling displays the real fantasy maximum, not a separate virtual health pool; a later unrelated display-scale change is preserved on exit.

Ordinary health, food, saturation, exhaustion and health-display state belong to the holder/session, not the character sheet. The session receipt acknowledges this snapshot before native mutation; versioned player PDC records its application/restoration phase. Voluntary exit restores ordinary gear/vitals at the current location, with ordinary health capped by the current legitimate maximum. Character damage is saved on the worker, and the character claim/receipt cannot complete ahead of that acknowledgment. Quit and disable perform the same best-effort cleanup. Failed or uncertain saves retain the receipt and acknowledged character record instead of permitting a stale-health reentry. Missing/ambiguous native proof never authorizes a snapshot heal; process-owned maximum overrides are still retired independently of equipment/client-write failures.

An intercepted active-character lethal outcome now creates one durable downed episode rather than immediately recording death. The episode captures its original session, holder, lethal attribution, downed anchor, entry-return location and absolute deadline. While it remains pending, native damage, healing, movement, displacement, inventory/control changes and ordinary gameplay actions are contained; logout and shutdown restore only safe ordinary-holder state while retaining the original countdown. Reconnect rebinds the same proved session at its saved anchor without resetting time. The bounded death menu can accept death, explicitly consume one character-owned Phoenix Down, or wait for the learned, aimed, any-party Revive. Phoenix Down is a persistent Rare Items product with global midnight-UTC stock 0–2, base price 1000 and the shared ±10% pricing policy; a newly delivered product appears only at the next scheduled or authorized restock. Purchase has no immediate effect and the character Inventory instructs use only while downed. An enabled exact-menu choice reserves one item against that episode, competes through the same single-outcome authority, restores full current fantasy health in place and applies the captured rare-phoenix-down-invulnerability-seconds deadline (default 5, zero allowed). Logout, restart, waiting, menu reopen, Accept Death, timeout and ordinary Inventory use never consume one automatically. A learned Revive or final death that wins leaves the item intact.

Accept Death and timeout compete with Revive for one persisted outcome. Successful Revive saves full current fantasy health, resumes the same active character in place and uses one absolute configurable protection deadline. Final death records the decision time—or the original expiry for delayed offline reconciliation—then applies frozen PvP reward/coin shares and party consequences exactly once before guarded Tail conversion. Ordinary health is restored before health-scale setters can send or queue client health packets, and return uses only the captured session-entry location. Refused or unavailable destinations retain explicit recovery evidence. New Tails retain the existing signed, authoritative death identity and readable static character history; stale or ambiguous conversion never overwrites another item or automatically reissues a book.

Interrupted recovery requires matching player/session receipt and native proof. It preserves current native modifier ownership and does not resurrect expired effects. Repeated/stale recovery cannot replay ordinary health or recreate an already released claim. This is not crash-atomic across player, inventory and plugin files; unresolved native setter phases and unacknowledged progress may require inspection. Legacy equipment-only receipts remain readable but do not grant health-restoration authority. Older binaries do not understand the new optional ordinary-vitals receipt field; do not downgrade across unresolved newer receipts.

The preceding equipment/progression checkpoint sections retain their historical evidence; current downed, Revive, nutrition, progression and combat behavior is governed by their later delivered stories and native integration tests.

Character inspection and inventory safety

The first-hotbar-slot book reads the active character's authoritative name, six stats, health, level, XP/MAX, points and learned identities. Inspection does not change character data or inventory. Plain-text pages wrap conservatively for the vanilla font without splitting Unicode code points. Large supported records use volumes of at most 80 inspection pages, leaving room for navigation and party controls while bounding each opened view's payload. This is a plugin presentation bound, not Minecraft's 100-page writable-book cap; signed books use a different codec. [Next pages] / [Previous pages] reread current data; unknown ability IDs and arbitrarily large saved numbers are not silently discarded.

Deferred reads and single-use navigation validate the player, session, character and held control. Superseded requests, ended sessions, quit/disable and invalid client tokens cannot open a stale view. Read failures preserve data and allow reopening. Native tests cover overflow, multiple volumes, rename, transfer, reload and unchanged inventory/data; actual client rendering and custom resource-pack fonts are not claimed as tested.

All nine hotbar controls remain bound, with seven selectable placeholders on a new character. Native containment tests cover export/consumption, hidden swaps, recipe autofill, armor/cosmetics, cursor custody and sorting relocation. The packaged inventory journey proves that these guards still permit the real book action and that removing the transferable offhand sheet restores ordinary equipment. Future attack/ability consumers must retain these same safeguards.

Character-book parties and combat protection

Use the slot-1 book to create a party, invite online players, accept invitations, leave, kick members or disband. Parties hold up to six consenting players. Only the creator/leader may invite, kick or disband; a leader leaving disbands rather than transferring leadership. Invitations last 60 real seconds and reserve no capacity. Book actions are single-use and bound to the current player/session/view, with authority and capacity rechecked on execution. Membership belongs to the player, not a traded character sheet, and grants no XP by itself.

A disconnected leader remains leader while another member is online. All-offline parties, invitations and book views are discarded on shutdown/restart. This does not persist or restore party rosters.

Native damage protection blocks friendly fire between active fantasy party members and player-attributable mixed-mode damage in both directions. Ordinary-versus-ordinary and environmental/mob combat retain their native rules and prior cancellations. Projectiles, TNT, fangs, clouds, fireworks and pet attacks retain player attribution; delayed fantasy sources cannot borrow a new session after exit, transfer or reconnect. Valid native healing is unchanged; stale projectile impacts are rejected before execution. Full RPG attacks and support/XP abilities remain separate unfinished work.

Attributable poison, wither and burning retain bounded source/recipient provenance in native entity/player PDC (combat_origin_v1, combat_dot_*_v1). Each damage tick checks current membership, source/recipient session identity and native effect state. Poison/wither hidden layers keep their own source rather than inheriting a covering effect's origin. Removal, expiry and unrelated replacement clear only the matching provenance; cleanup does not remove native effects or change health, equipment, character YAML or XP. A tick-driven cleanup checks only the three relevant player metadata keys and stops on disable. Native PDC serialization retains source proofs across reload; ended fantasy sessions remain invalid, while parties themselves never survive restart. These are native save guarantees, not cross-save atomicity or dedicated cross-plugin integration.

Automated party tests exercise actual packaged listeners, native book custom-click payloads, native event dispatch, effect-merging semantics and PDC serialization. Player/network/world execution remains represented by explicit test ports; live multiplayer testing and deployment are separate.

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.

The documented crafting/cleanup boundaries are automated-test evidence, not cross-save atomicity, complete RPG balance, live multiplayer or production-compatibility verification. 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%