Skip to main content

FTB cross-version record

2026-11-08 master: unified directory for world-save-managed data (1.21.1 pending sync)​

Files stored outside the world save all live under system/; player business data is players/<UUID>.dat, conversations are chat/<UUID>.dat, and the shared and private databases are database/shared.dat and database/players/<UUID>.dat; all of them now use FileAccess.NBT_COMPRESSED GZIP compression. When porting, sync AssistantServer's root directory, FileAgentStore's read / write / enumerate / delete, the AgentDatabases file names, AgentDatabase's NbtOps / strict loading, AtomicFiles and the logging NBT metering at the same time. The usage and AI permission SavedData under data/system are unchanged, and client config and voice files are unchanged; at the user's request no old directory is migrated or deleted. master compiles; the game was not started for verification.

2026-11-06 card node and string-button rework (master, pending port to other branches)​

This section supersedes the structures described below that used the old CardGrid / dedicated business nodes. There are now 18 official node types, split into packages by function; quests, shop and lottery use the generic GridNode + GroupsNode / FlexNode; CardPagination is the only pagination component, and CardUiState replaces CardPageState. The quest list keeps in-result chapter grouping, the original numbering, the three-row fixed area and the return state; the pin uses a plain button + QuestCardBindings to build events, and DisplayCardCache maintains live state. Do not port the deleted GroupedActionsNode / OfferGridNode / PrizeGridNode / QuestPinNode again.

Buttons uniformly use click/value, and return-val carries only CardReference and a string; the server publishes CardButtonEvent first, and when nothing handles it, CardEventValue parses it into the existing ModuleRouting.button. URLs go through the vanilla open flow, while local still binds its callback in code. When porting, also sync the shared CardAction / request StreamCodec, the server event and parser, the client controller, all ability interfaces and nodes, the 27 JSON files, the pin SVG, the CSS, the offline editor and the documentation; copying only the templates leads to parse failures. The network protocol number stays 1, and both sides must use matching builds. See cards.md.

2026-11-06 master: shared three-row paged grid (1.21.1 pending sync)​

This section supersedes the old "10 items per page" quest layout convention described below. The client gains CardPagination and CardGrid: the pagination that used to live in ActionsNode moves into the former, while the latter controls the column count, the three-row fixed area and the columns × max_row capacity; GroupedActionsNode / OfferGridNode / PrizeGridNode share it, with 18 items in full mode and 9 in notifications by default. Short results, empty lists and the last page keep the area and the pagination buttons, and a single page greys out both buttons; DOM, icons and tooltips are created only for the current page. The original business indices, CardPageState, notification detail return and completed-notification semantics are preserved.

When porting, sync the two shared classes, the four nodes, quest_list.json, the shared CSS (removing the old grid max-height clipping and the notification column-count override), the documentation and the test task description. The three grid nodes merge CardGrid.CODEC into flat fields, and the quest template uses max_row: 3 instead of page_size: 10; the existing max_row of shop and lottery changes from height-limited scrolling to pagination. Do not copy only the JSON/CSS, or the layout column count and the pagination capacity will disagree. No registry, dependency or network data format is added; compilation passes, but this has not been accepted in game yet.

2026-11-05 master: grouped browsing of the quest list (1.21.1 pending sync)​

QuestBriefOutput adds chapterId, groupId and the group name on top of the original chapter name; FtbQuestAccess.brief reads this metadata only from getChapter().getGroup() of each matching quest, and does not enumerate all chapters or chapter groups separately. The 26.1.2.8 source was checked: Chapter.getGroup returns a ChapterGroup, the default group uses isDefaultGroup and shows no extra book name; getAllChapters in a normal query is ordered by chapter group first, then by chapter.

QuestList writes the grouping parameter for the button; the client gains the official node system:grouped_actions, whose left side lists the non-empty chapters in the result together with their parent groups, and whose right side reuses the shop shelf structure to show quests, reusing the 10-items-per-page local pagination of ActionsNode. CardPageState gains the selected chapter, page numbers are saved per chapter key, and PUSH / back keep the original position; the global quest numbering and the server list snapshot are unchanged. Multi-unlock shares this template, and the completed notification and single-unlock logic are unchanged.

The 2026-11-06 layout fix must be ported as well: the left column uses a content container with natural height that scrolls only beyond the limit and keeps no gutter; the right side uses a real offer_grid > offer_cell > offer_cell_label, with 6 columns and 104px cells in full mode and 3 columns in the HUD. The duplicated chapter name and entry count on the right are removed, the shelf goes straight to the top of the content area, and chapter information stays only in the left column. GroupedActionsNode gains columns / cell / children and renders the title, number and progress in blocks through generic nodes; ActionsNode separates row rendering from pagination and shares the action binding. The top-level title in quest_list shows "Quest list" on its own, the hint becomes a short line, and long summaries stay only in the data and narration paths.

The same day added centering and quest icons: QuestList writes action_quest_id into the action; system:quest_icon / QuestIconNode and a public registry field are added, calling FtbQuestIconElement.createQuest through reflection and reusing the existing native drawing and tooltip. The client first checks ClientQuestFile.exists, then gets Quest.getIcon by ID; in 26.1.2.8 that method prefers the configured rawIcon, then the theme, then the target's default icon. The icon is omitted when FTB is not installed or the quest is unavailable. The template adds a 32px icon and merges the number / progress into one line; the shared CSS centers the title and the inner formatted row block explicitly. When porting, sync the new node, registry, adapter, action parameters, JSON and CSS; QuestBriefOutput does not need extending and the network protocol does not change.

The port must sync the shared DTO / Codec, the FTB brief construction, QuestList, GroupedActionsNode and its registration, the shared pagination entry of ActionsNode, CardPageState, quest_list.json, the shared CSS and the bilingual keys; copying only the resource files is not enough. No dependency, network request or quest progress operation is added. compileJava / processResources were run; Minecraft was not started and no functional acceptance was performed.

A further 2026-11-06 addition guards the left column against scrolling with no range: AUI 1.2.9's MouseEvent still selects an overflow:auto element when no element in the path has a real scroll range, and ScrollModel then produces an elastic displacement. GroupedActionsNode registers an internal wheel listener on the left column after initialization and calls preventDefault only when no element in the path has an actual scroll range; long chapter lists and the outer chat scrolling are preserved, and Shift+wheel also considers the horizontal range. When porting, check the target AUI's event and range APIs; do not just copy the overflow:auto style, and do not query scrollLimit inside layout callbacks.

The same day added the pin and bottom progress bar of the quest cell: the client nodes QuestPinNode / ProgressNode and public SystemCardNodeTypes registry entries; quest_list.json turns the percentage text into a progress bar, keeps the number, and adds a pin in the top-right corner. The old version here used the per-connection QuestPinCache; the current version has switched to the server-side DisplayCardsDataStorage, see the persistent card record at the end of this page. The port must still check the AUI Document.getAll, SVG and event APIs, and sync the two new nodes, the network registration, JSON, the shared CSS and the bilingual keys. FTB progress, DTOs and the submit protocol are unchanged.

The later pin fix must be synced as well: the shared CSS tilts the SVG by 35° by default and resets the rotation to zero when aria-pressed=true; a 22px button / 16px icon, transparent and without a frame, takes precedence over the AUI theme's button state selectors so that the theme's minimum height and selected background do not produce a visible block. QuestOutput.buildCard gains a context parameter for an existing id, quest.json adds the quest_detail_card style and the quest_pin node, details and the list share the same pinned list, and no serialization field is added.

Record date: 2026-10-30. The feature changes of 1.21.1 have been synced to Minecraft 26.1.2 / master. The source is b601fd7..03ca46a; master was switched first, then git pull --ff-only was run, and the target was updated from 2c459f9 to b6afc9d before the changes were merged. Nothing was committed or pushed this time.

Sources and scope​

The 1.21.1 development dependencies are Quests 2101.1.37, Library 2101.1.39 and Teams 2101.1.11. The original problem came from the user's modpack D:\PrismLauncher\instances\『化龍』导入包-1.4\minecraft: its Quests / Library / Teams are 2101.1.34 / 2101.1.35 / 2101.1.10, and the existing logs load 24 chapters, 837 quests and two languages. Only definitions, dependencies and logs were read; neither the modpack nor the world save was modified.

For example 359B897165997027 ("Credits list") combines a checkmark objective, items and 100 experience, which exposed the problem that the old gift classification accepted only item rewards. The title problem, in turn, comes from the server-side shared fallback language; a direct getTitle cannot replace resolving the player's language.

Only features are ported; the 1.21.1 version downgrade, embedded Saturn, ItemStackTemplate compatibility and import cleanup are not brought over. The target keeps Java 25, NeoForge 26.1.2.114, AUI 1.2.6, external Saturn, and Quests / Library / Teams 26.1.2.8 / 26.1.2.9 / 26.1.2.4. The notification immediate replacement, manual switching and quest-completion check/cross display that already existed after the pull are kept.

Synced features​

FeatureImplementation entry point and behavior
Player language and formatted textFtbQuestText calls TranslationManager with the player's language and keeps the missing-translation fallback; it covers lists, search, chapters, details, submission, notifications and narration. Untitled quests recursively take the title of the first objective, Component / § / & are handled uniformly, and the shared locale or title cache is not modified.
Checkmark quests and gift classificationFtbQuestReader matches the native type exactly: not repeatable, all objectives are checkmarks, at least one objective and at least one non-empty item, optionally with experience / levels; hidden, command, choice, random and third-party rewards keep the normal handling. A quest with no objectives, or with only checkmark objectives and no rewards, counts as an introduction quest and does not enter the progress list.
Native type icons and tooltipsQuestIconOutput prepares the quest / reward type IDs, localized text and counts on the server; the client system:icons loads the FTB icon elements through reflection. Items still use the vanilla item tooltip, non-item rewards reuse the FTB type icon and text tooltip; when the dependency or type is missing it falls back to text.
Compact reward layoutQuest details, completed notifications and gifts show the reward title only once; items and type icons are laid out in a horizontal row that wraps when too wide, and choice options keep their number and action.
Unlock summaryThe per-item completed notifications for the batch are sent first, then the gift summary and the normal unlock notifications separately; a single normal unlock opens the detail directly, multiple unlocks merge into a list, and each recipient gets at most one per batch. List notifications show 10 items per page with local paging; after viewing a detail by ID you can return to the original page, and it does not overwrite the ID of an active query. Relogging, resetting the baseline and silent operations do not resend old unlocks.
Guide searchOnly quests that are checkmark / have no objectives, have body text and are not gifts are included; guideCandidates / guideReadable include introduction quests, and chapter and quest visibility, detail unlock and body hidden conditions are still checked. Before returning, it goes back to the server to re-check the team, language, quest object and content.
Claiming gifts from chat historyThe "Claim gift" button and the local "Confirm" are kept; a gift token no longer expires after two minutes within the same connection, and the player, team, quest / reward object, completion time and native state are still validated. Logging out, revoking or disabling the module cleans it up, the short-lived tokens of normal rewards are unchanged; disk history does not re-authorize.
Reward totalsFtbQuestPreviews merges identical items and components, counts / random ranges, and experience and levels; six stacks of 100 experience show +600. The item template count is 1, only_one conditional items are counted separately, and nothing is rolled or granted early.
Tiered local databaseagent.database provides the same Codec data tables and sparse TF-IDF API for the world-shared and per-UUID databases; they are stored in <world>/system/database/shared.dat and players/<UUID>.dat using GZIP-compressed NBT. revoke / reset deletes the private database and the cumulative usage is unchanged; no new model, native code, external service or dependency is added.
FTB reindexing/system ftb reload requires administrator permission and can be run from the console; it re-collects the FTB definitions that are already loaded and rebuilds the index, without reading or writing native quest files or progress. The cache invalidation event cancels results from the old generation.
Database-first Q&AThe core system:question first queries the current player's / shared knowledge and the authorized guides of enabled FTB with database.query. When material is found there is at most one model pass, and on failure the raw text can be shown; a storage error is not treated as an empty result. WebResearch runs only when the database query is empty, the search module is enabled, the network is explicitly allowed for this request and the external service is on.
Search tools and authorizationSearchSystemModule is a ToolModule and exports search / fetch; Descriptor.optionalTools carries the permission of the module the tool belongs to, so a missing search does not affect local Q&A. /question only queries locally, /search allows going online with an empty result, and the default test tag no longer grants search automatically. The budget key is system:question.

26.1.2 API adaptation​

This time the target dependency source JARs were checked directly, extracted to build/ftbsrc/26.1.2.8, build/ftblibsrc/26.1.2.9 and build/auisrc/26.1-1.2.6; the Minecraft signatures were confirmed from the actual 26.1.2 JAR.

  • Server instance: the target's ServerQuestFile.getInstance() returns a non-null instance rather than an Optional; the new entry point checks exists() first and does not keep the Optional calls from the source code that do not apply.
  • Native events: master's NeoForge FTBQuestsEvent and ProgressEventData / ProgressType are kept, and the Architectury events are not brought over. ClearFileCache locates the bridge by ServerQuestFile and also invalidates the guide generation.
  • Claim order: normal rewards are still handed to TeamData.claimReward. The target Reward.claim returns void and there is no tryClaim; choice rewards strictly follow the target ClaimChoiceRewardMessage, calling claim on the selected reward after a successful markRewardAsClaimed, and finally checking only the native claimed flag — it does not claim that the third party actually delivered the reward and offers no rollback guarantee.
  • Reward data: the target writeData accepts a Json5Object, reads xp / xp_levels / random_bonus / only_one with Json5Util, and does not use the 1.21 CompoundTag. Item previews use the vanilla immutable ItemStackTemplate and merge by item and component equality.
  • Text decoding: a JSON Component is read through ComponentSerialization.CODEC and the registry JsonOps; when it cannot be parsed the literal is kept, and the FTB client text helper classes are not loaded.
  • Client drawing: the target type uses getIcon, and the Icon renderer records render state into GuiGraphicsExtractor. The new FtbQuestIconRenderer runs in the AUI BODY phase, replaying geometry, text and items into the current AUI output target, transform and projection and reusing the active scissor; it does not call the full GuiRenderer, which would reset the target. Tooltips use setTooltipForNextFrame. There is no old GuiGraphics / flush code and no global GL depth recovery code.
  • Block icon depth: replaying an item model uses Base.getGuiItemModelZ() and cannot reuse Z=0 from 2D icons; the backward part of the model intersects the card background and looks as if faces were culled. The vanilla model culling rules are kept, and the item buffer is submitted before GUI lighting is restored; text such as counts is drawn after the model, using Base.getGuiItemDecorationZ() to keep the foreground layer.
  • Commands and naming: the native Commands.hasPermission is used; public resource IDs use Identifier. Optional FTB classes stay isolated in compat.ftb, the dedicated server does not reference the client icon implementation, and optional classes are not loaded when FTB is not installed.

Data formats​

1.21.1 native quests use .snbt, with languages in quests/lang/<locale>.snbt. 26.1.2 keeps using the target's native .json5 examples and layout; the old examples, the run directory and the modpack files were not copied over this time, and no old world save migration code was added. Guides and backgrounds are rebuilt on demand from the new library, and the old standalone cache files are no longer read or written.

Verification and pending acceptance​

gradlew compileJava testModClasses was run, and the main code and the test source set compile; finally gradlew compileJava testModClasses jar --console=plain succeeded and produced a 1,542,493-byte JAR; the 513 keys per language, 23 cards and 13 node types were verified. For the full static check and the final results, see Compilation and acceptance. Minecraft was not started, tests were not run, no world save was created and no paid API was called, so this is not evidence that in-game acceptance has passed.

Manual acceptance on the target version remains:

  1. Quest titles / descriptions / search / notifications / narration for Chinese and English players, plus missing translations, untitled quests, and JSON, § and & text.
  2. Checkmark and item-plus-experience gifts, exclusion of introduction quests, normal quest submission, native dependencies / pause / order / auto-claim and team sharing.
  3. Icons, counts, item and text tooltips, third-party types, scroll clipping, fade-in compositing and the T-key interaction; history text fallback when FTB is missing.
  4. A single unlock opens the detail directly, multiple unlocks merge into a list of 10 items per page; local previous / next page sends no packet and switches no notification, and returning from a detail goes back to the original page. Completed notifications are sent per item and take priority, the bottom Confirm closes the whole window, and the end of narration does not page automatically.
  5. Claiming a gift later from chat history, repeated clicks and partial failures, totals for experience / levels and component items.
  6. Isolation and persistence of the shared / private databases, reload permissions and concurrent invalidation, database error handling; no search when a local result exists, going online only when the database query is empty and the request explicitly authorizes it, and disabling the search module does not affect local Q&A.

Unless explicitly requested, no game is started, probe is inserted or temporary data created for the acceptance above.

Later master change: interactive quest notification window​

The pagination and return behavior above were added to master on 2026-10-30. When syncing other versions later, also sync ActionsNode's notification_page_size, the CardPageState / return stack held by notifications, CardButton.Behavior.PUSH and its association with the request source, quest_list.json, the shared CSS and the four bilingual keys; the single-item branch of the server unlockNotification reuses FtbQuestAccess.detail(..., "unlocked"), and the completed event loop does not merge. The change adds no dependency, network packet or database format.

2026-10-30 additional port in this round​

The pre-port check records are kept below first; the actual implementation results are in the following subsections.

2026-10-30 pending port checklist review (1.21.1 7aca25a → master b97aa1c)​

This time only sources, dependency sources and resources were read and compared, and git fetch origin was run to update remote refs; no branch was switched, master was not modified, nothing was compiled and no game was started. The remote master is currently b6afc9d; the extra b97aa1c in the local master has not been pushed yet, and the table below uses the local master as its baseline.

StatusScopeCheck result and follow-up
Pending syncscreen/card/node/ActionsNodeElement.init is called before the callback is captured. 26.1 / AUI 1.2.6's Node.prepareForInsertion and Element.init were checked directly; the target version likewise replaces plain Elements, and the pagination buttons have the same problem.
Pending syncspeech/asr/RecognitionRecording, AssistantRecognitionSync the stop/enqueue mutual exclusion, EOF directly after the buffer is exhausted, terminal-callback protection, the 90-second session / 15-second drain timeout, cancellation cleanup and executor rejection handling; all platform-neutral, with no Windows IME involvement.
Fix first, then syncThe natural EOF of RecognitionRecording.recordCurrently ended = true is written in the outer finally of the try-with-resources, which actually runs after the device close(), so "publish the natural EOF before the device closes" is not implemented yet. A manual stop already returns EOF through stopped; for a natural end the publish action should be moved before the resources close, and then both versions synced. This entry only records the problem; the implementation was not changed.
Confirm against target dependenciesImBlockerConfigMixin, system.mixins.jsonThe current allowlist injection was only verified against IMBlocker 5.6.1 / 1.21.1 and does not mean it applies to 26.1.2; if target-version integration is needed, check the actual JAR first, and do not port it when no usable version exists.
Keep the target implementationAssistantInputScreen, TokenUsageData26.1.2 already has a native text input / preedit lifecycle, which must not be deleted together with the 1.21.1 simplification; SavedDataType uses a valid Identifier, so the 1.21.1 string-filename fix should not be applied either. The Windows-specific IME detection has been removed and is not added to the target version.
SyncedQuest notification window and FTB businessPUSH / the return stack, 10 items per page, a single new quest going straight to its detail and separate notifications for completed quests are all already in the local master; this round found no difference between the two branches in the assets/system cards / language files and the shared CSS. FTB events, reward calls, ItemStackTemplate and icon rendering still keep each version's API.

Update the ASR and pagination notes when syncing, and keep 26.1.2's own version information and its rendering and IME documentation; do not overwrite the 1.21.1 documentation wholesale or cherry-pick 7aca25a directly.

Implementation results​

master was switched and git pull --ff-only origin master was run; there were no new remote commits and the baseline is still b97aa1c. The following were synced from 7aca25a: the earlier initialization in ActionsNode, the stop / frame-queue handling in RecognitionRecording, and the terminal state, timeouts, cancellation and error handling in AssistantRecognition; in addition, ended = true on a natural end was moved before the try-with-resources closes the device, with the outer finally still guaranteeing that the terminal publication happens even on the exception path. This additional ordering fix is currently only on master, and 1.21.1 needs to be synced back later.

26.1.2's AssistantInputScreen native text input / preedit, TokenUsageData's Identifier / SavedDataType, and the version, dependency and Mixin configuration keep the original implementation. On 2026-10-30 the IMBlocker 26.1.2 version list on Modrinth was empty, so the allowlist Mixin that had only been verified against 1.21.1 was not added to the target; no platform-specific IME code was added.

gradlew compileJava testModClasses jar --console=plain was actually run and succeeded. Static / artifact checks: 517 keys per language with identical sets, 23 card JSON files parse, and all three modified classes are Java 25 bytecode; system-1.0-beta.1.jar is 1,548,823 bytes and contains no embedded Saturn, old screen/input classes or IMBlocker Mixin. Tests were not run, no game was started, no world save was created and no paid API was called; pagination clicks, device end and cancellation, and automatic sending of recognition results still need in-game acceptance. The changes were not committed or pushed.

2026-10-31 full-mode pagination, positioning and speech billing sync​

All working-tree content of 1.21.1 was committed as c6fc689; then master was switched and git pull --ff-only was run, there were no remote updates, and the target baseline is 1ef5d80. This time the two versions were compared item by item, and only the shared fixes that master still lacked were synced:

ScopeSynced content
Full-mode quest listActionsNode.page_size=10 is used for both full mode and notifications; AssistantScreen saves the page number per card UUID and passes it in through AssistantView, so chat refreshes and window resizes keep the state. The buttons only redraw local nodes and send no packet or advance notifications.
UI positioningThe shared CSS fixes the full-mode position directly to the viewport, and compact mode and notifications explicitly override top: auto, keeping the bottom anchor.
Missing pageAssistantScreen.updatePage checks the target page node and lets custom templates omit module pages, avoiding a null pointer. The target CoreSystemClientModule no longer has the old chat entry, so no repeated change is needed.
Speech price tableSpeechPricingConfig initializes PRICE / LOGGER first, then builds the outer Codec / INSTANCE; amounts accept a JSON number or a decimal string, and numbers are stored directly as BigDecimal.
Statistics isolation and loggingLocalSpeechUsage.Ledger isolates RuntimeException / LinkageError in one place inside the synchronous update, keeps the previous snapshot and does not block a successful recognition or narration; AssistantRecognition keeps the full exception stack.

26.1.2's player model is already submitted inside AUI through a separate LivingEntityRenderState and FeatureRenderDispatcher, so it does not need to be moved into the 1.21.1 legacy entity rendering call. master's publishing of the natural EOF before the device closes, the native IME, SavedDataType, external Saturn, versions and dependencies are kept; the 1.21.1 annotations, ResourceLocation or rendering implementation are not copied. Neither README was changed and nothing was pushed to the remote.

2026-10-31 speech ledger background processing (1.21.1 pending sync)​

The user reported a brief freeze after clicking the lottery that then recovered, with no speech exception in the logs. Reading the source confirmed that TTS HTTP / SSE / decoding run in the background, but AssistantSpeech.start reads local files synchronously through LocalSpeechUsage.current() when it builds the ledger, and the usage snapshot and the price calculation / disk write share the ledger lock. master moves the ledger's first load, calculation, merge and disk write together onto the system-speech-usage single-thread queue; the first load is queued before the updates, the UI reads an immutable snapshot through a volatile field, and callback deduplication and account isolation are unchanged. On JVM shutdown an independent hook drains the queue for at most three seconds and never waits on the render thread. The original LocalSpeechUsage content is the same in both versions, so 1.21.1 can sync this class and the speech documentation later.

This conclusion is a blocking risk found by source audit and does not mean it is confirmed as the only root cause of this freeze. When thread stacks were captured read-only from the user's existing Prism 1.21.1 process, the render thread was already waiting in the normal frame limiter and the freeze itself was not captured. The game was not started or operated, no runtime probe was run and no paid API was called.

Later change to persistent pinned quests​

The per-connection list of the earlier QuestPinCache / QUEST_PINS has now been replaced by the core module's DisplayCardsDataStorage and ClientModuleCaches.DISPLAY_CARDS. Pinning goes through dedicated C2S/S2C packets; the server saves source + id per player transaction, and after markDirty it reuses the player file persistence; on login it rebuilds the visible quest snapshot of the current team, refreshes every second and sends only changes. Pins are kept after completion, hidden when permission is lost, logging out only clears the client cache, and revoke / reset deletes the record.

For later ports, check AssistantCardDisplays, FtbQuestDisplay, QuestPinOutput, the storage and network registration, AssistantCardDisplay, AuiHudEntry / DisplayCardsHudEntry / SystemHudEntries, the passive mouse mixin exclusion, quest_pin.json, assistant-cards.html, the shared CSS and the bilingual keys together. Currently the change exists only in the 26.1.2 workspace and has not been ported to 1.21.1; do not restore the client-side temporary list according to the earlier records above.

2026-11-07 runtime lifecycle cleanup (1.21.1 pending sync)​

AssistantServer becomes a single volatile INSTANCE that is published after the server has finished starting and is cleared before shutdown; the object-identity check in get(server) is kept, and no fields are attached to MinecraftServer and no Mixin is added. FTB no longer uses the static SERVERS / ACCESS maps: FtbQuestRuntime holds the unified queries, knowledge index and notification state; ModuleContext.sharedRuntime shares an instance within one installation, and ModuleManager deduplicates validate / tick / close by object identity and handles resource closing when installation fails. Player sessions are counted per configuration, and the player reference is cleared only when the last one closes.

Later syncs must change FtbQuestAdapter.runtime's ModuleContext parameter, the QuestSystemModule reflection signature, the explicitly submitted instance notification calls and the runtime lookup of /system ftb reload together. FTB notifications become instance @SubscribeEvent listeners that are registered on install and unregistered on close; the old version adjusts them to the actual event API, keeps optional dependencies isolated and does not go back to a server-keyed Map. Currently only master is changed and 1.21.1 has not been synced; the compilation result does not mean runtime events and world-save switching have been accepted.

In the same round ModuleContext keeps AgentEngine / the module registry as fixed fields read through engine() / modules(), and other service injection moves to ContextType<T> keys and a Map. AssistantServer uses a two-argument constructor plus chained put; FTB and other modules use fixed getters or context.get(ContextTypes.XXX) according to their dependencies. ModuleManager publishes ModuleContextEvent before actual assembly so other mods can inject extension services synchronously. When porting, sync ContextType / ContextTypes, the event and the call sites together, keep the original sharedRuntime installation scope and lifecycle, and do not restore the old constructor that adds a field per service.