promotional bannermobile promotional banner

Runic Skills

Runic Skills is a Minecraft 1.20.1 Forge RPG progression mod that adds ten levelable skills, auto-scaling passive bonuses, and hundreds of toggleable perks so players can shape a real build through normal gameplay. Designed for the Runecraft modpack.

File Details

Runic Skills 1.3.2 (Forge 1.20.1)

  • R
  • May 15, 2026
  • 1.26 MB
  • 455
  • 1.20.1
  • Forge

File Name

runicskills-1.3.2.jar

Supported Versions

  • 1.20.1

Curse Maven Snippet

Forge

implementation fg.deobf("curse.maven:runic-skills-1516283:8091030")
Curse Maven does not yet support mods that have disabled 3rd party sharing

Learn more about Curse Maven

Changelog

[1.3.2] - 2026-05-15

UI readability hotfix on top of 1.3.1. The skill panel's header text (skill name, level/rank, player name + XP, "Choose Your Title") renders on a light gray panel background but uses Utils.FONT_COLOR = Color.WHITE — low contrast and noticeably hard to read on most monitors. 1.3.2 flips FONT_COLOR to Color.BLACK, restoring legibility everywhere this constant is used. Pure cosmetic; no behaviour change.

Changed

  • Utils.FONT_COLOR flipped from Color.WHITE.getRGB() to Color.BLACK.getRGB(). Single-constant change. Affects the detail-page header (skill name, level/rank string), the overview-page header (player name, XP level), and the titles-page header ("Choose Your Title") — every render site that routes through Utils.drawCenter(...) or directly references Utils.FONT_COLOR. Other white-on-dark text in the GUI (skill level in overview grid cells, current-title button text, perk-level overlays over icons, HUD overlays over the game world) is unchanged — it renders on dark backgrounds where white is the correct choice.

Notes

  • Save-compatible with 1.3.x and 1.2.x. No NBT, no PROTOCOL_VERSION, no protocol packets, no config schema, no perk behaviour changes — pure cosmetic.
  • Tested: ./gradlew build passes; ./gradlew checkSidedImports passes; no resource pack textures touched (light panel art unchanged, text-color flip is code-only).

[1.3.1] - 2026-05-15

Log-noise hotfix on top of 1.3.0. 1.2.1's @Pseudo fix for the MixTargetFinder / MixGunItem WARNs silenced Mixin's own "@Mixin target was not found" line but left Forge's classloader-side "Error loading class" WARN firing on every startup when BetterCombat or PointBlank are absent — confirmed in the 1.3.0 smoke-test log (and present identically in 1.2.1 and 1.2.2 logs). 1.3.1 closes that residual gap with a Mixin Config Plugin (IMixinConfigPlugin) that excludes those two mixins entirely when their target mods aren't installed, so Mixin never asks the classloader for the missing target's bytecode and neither WARN can fire from any source. Pure log noise; zero functional change.

Fixed

  • Error loading class: net/bettercombat/client/collision/TargetFinder and ... com/vicmatskiv/pointblank/item/GunItem WARN at startup when BetterCombat / PointBlank aren't installed. @Pseudo (added in 1.2.1) only suppressed Mixin's @Mixin target was not found WARN; the earlier TransformingClassLoader "Error loading class" WARN fires independently when the class.class literal in @Mixin({TargetFinder.class}) triggers a bytecode lookup against the missing class. New RunicSkillsMixinPlugin implements IMixinConfigPlugin returns false from shouldApplyMixin(...) for these two mixins when their target mods aren't present in LoadingModList, so Mixin never asks the classloader for the target bytecode and the WARN can't fire. Pre-existing since the BetterCombat / PointBlank mixins were introduced; partially fixed in 1.2.1, fully fixed here.

Notes

  • Save-compatible with 1.3.0 and 1.2.x. No NBT, no PROTOCOL_VERSION, no protocol packets, no config schema, no perk behavior changes — pure log noise.
  • @Pseudo retained on both mixins as defence-in-depth: if a future runtime bypasses the plugin gate (e.g., via JVM args), Mixin still treats the missing target as optional rather than throwing.
  • runicskills.mixins.json now declares "plugin": "com.otectus.runicskills.mixin.RunicSkillsMixinPlugin".
  • Tested: ./gradlew build passes; ./gradlew checkSidedImports passes; on the user's CurseForge instance (no BetterCombat, no PointBlank installed) the 1.3.1 install over the 1.3.0 install should show zero Runic Skills WARN/ERROR lines in a 60s post-spawn idle window — a clean diff against the two residual "Error loading class" lines visible in the 1.3.0 smoke-test log.

[1.3.0] - 2026-05-14

Data-driven content release. Two headline features: custom skill visuals (datapack-driven overrides for skill overview/detail/background art with full namespaced-id support) and an FTB Quests integration (six native task types — skill_level, global_level, perk_rank, passive_level, title_unlocked, title_selected — wired through a save-isolated quest bridge). KubeJS perk/passive helpers now accept arbitrary namespace:path texture ids so pack scripts can point at any mod's item or texture sprite without copying assets. Save-compatible with 1.2.x — no NBT, no PROTOCOL_VERSION, no protocol packets, no config schema drift; both new features are entirely additive and absent-when-disabled.

Added — Custom skill visuals

  • data/<namespace>/runicskills/skill_visuals/<id>.json datapack folder (new). One JSON per skill with optional fields overview_icon, detail_icon, background. Any field defaults through to the legacy hardcoded asset, so authors can override a single slot without re-supplying the rest. Loaded via the new SkillVisualsReloadListener (extends SimpleJsonResourceReloadListener, modeled on the existing PerkGroupsReloadListener); reloads pick up overrides through /reload like any other datapack data.
  • HandlerResources.parseTexture(String) — new namespace-aware parser. Accepts either namespace:path (vanilla ResourceLocation syntax) or a bare path (preserved as runicskills:<path> for parity with the legacy HandlerResources.create(...) callers). Invalid ids log a single WARN and resolve to NULL_PERK. The legacy create(...) static stays at line 698 unchanged — addon mods calling it keep working.
  • Perk.add(...) and Passive.add(...) KubeJS helpers route their texture argument through parseTexture(...). Existing scripts using path-only strings see zero behavioral change; new scripts can pass botania:textures/item/lexicon.png, ars_nouveau:textures/item/jar_of_light.png, etc.
  • SkillVisuals record + accessors on Skill. New getOverviewIcon(), getDetailIcon(), getBackgroundTexture() methods read the override layer with fall-through to the legacy lockedTexture[] / background defaults. The progressive 4-tier locked icon array is untouched — overrides are a single static slot per skill by design, matching the spec's per-skill semantics.

Added — FTB Quests integration

  • Six task types registered via TaskTypes.register(...):
Task id Fields Completes when
runicskills:skill_level skill, required_level named skill ≥ required level
runicskills:global_level required_total sum of all skill levels ≥ required total
runicskills:perk_rank perk, required_rank named perk's rank ≥ required rank (1 = enabled)
runicskills:passive_level passive, required_level named passive ≥ required level
runicskills:title_unlocked title named title unlocked on the player capability
runicskills:title_selected title player is actively wearing the named title
  • Sticky completion by default. Once a task completes it stays complete even if the player's underlying state drops below the threshold (the standard FTBQ "checked off, stays checked" UX). Pack authors who want live-threshold semantics — task progress reflecting current state, including regressions on respec / passive-down — add "sticky": false to the task JSON.
  • RunicQuestBridge always-loaded facade. Static no-op-by-default delegators that the network/event layer calls unconditionally. When FTB Quests is absent the facade dispatches every call to a NOOP listener (one volatile read + one empty virtual dispatch — measurable cost is essentially zero). The reflectively-loaded FTBQuestsIntegration installs the real listener on construction; no FTB types ever enter the always-loaded constant pool.
  • enableFTBQuestsIntegration master toggle in HandlerCommonConfig (default true). Matches the 1.2.0 per-integration toggle pattern: when false, task types are not registered and the bridge stays no-op.
  • Bridge wired into every authoritative mutation site: SkillLevelUpSP (post-addSkillLevel), PassiveLevelUpSP / PassiveLevelDownSP, SetPlayerTitleSP, Title.setRequirement (post-unlock). TogglePerkSP is consumed via the existing 1.2.0 PerkToggleEvent.Post rather than a fifth inline call.
  • Login + clone + respawn backfill. PlayerLifecycleHandler.onPlayerJoinWorld and onPlayerClone, plus RespecCommand, call RunicQuestBridge.refreshAll(player) so quests author quests retroactively against players who are already qualified, and so non-sticky tasks regress correctly on respec.
  • Optional compile-time dependency. compileOnly fg.deobf("maven.modrinth:ftb-quests-forge:2001.4.9") + ftb-library-forge:2001.2.6. mods.toml declares ftbquests as mandatory = false, versionRange = "[2001.4,)". The Modrinth Maven was already in the build.

Changed

  • RunicSkillsScreen detail-page state cached per render frame. buildDetailPageState() previously sorted passives/perks and rebuilt the row layout in both drawDetailBackground() and drawDetail() on every render frame — duplicate work per frame across render(). A small per-frame cache (invalidated at the top of render() and populated lazily by getDetailPageStateForRender()) collapses that to one rebuild per frame. Click handlers still call buildDetailPageState() directly to avoid serving stale layout after a click changes page state.
  • Skill render call sites swapped to the new accessors. RunicSkillsScreen lines 257/282/302 now read getOverviewIcon() / getBackgroundTexture() / getDetailIcon() instead of getLockedTexture() / background directly. Legacy defaults are preserved through fall-through, so packs without a skill_visuals JSON see no visual change.

Notes

  • Save-compatible with 1.2.x. No SkillCapability schema changes; no PROTOCOL_VERSION bump; no new Runic Skills packets. FTB task progress lives in FTB Quests' own NBT.
  • Without FTB Quests installed: zero classloading errors, no Loaded integration ... ftbquests log line, bridge stays in NOOP mode. The mod boots clean on a vanilla-Forge + Runic-Skills modlist.
  • Asset caveat for custom skill visuals. Texture ids must point at assets the client has — datapack overrides on a dedicated server don't conjure client textures out of thin air. Pack the assets in the same resource pack as the datapack JSON.
  • Asset validation. SkillVisualsReloadListener calls manager.getResource(loc).isPresent() on each override at apply time; missing assets log a single per-file WARN rather than letting users find pink-black sentinels in the menu.
  • Version matrix. Runic Skills 1.3.0 / Forge 1.20.1-47.3.0+ / FTB Quests Forge [2001.4,) (tested against 2001.4.9). Java 17.
  • Public Forge event API is unchanged. 1.2.0's SkillLevelUpEvent, PassiveLevelUpEvent, PerkToggleEvent.Pre/Post, TitleEarnedEvent all behave identically. Post variants for skill/passive events are deferred to a future minor; the inline bridge calls cover what FTB Quests needs without an API change.

[1.2.2] - 2026-05-14

Second log-noise hotfix. The 1.2.1 fix for ServerNetworking.acceptsVersion used NetworkRegistry.ABSENT.equals(peerVersion) but the predicate's WARN still fired every ~5 seconds in single-player. Either Forge 47.3.0's NetworkRegistry.ABSENT constant value drifts from the open-source value (likely the trailing 🤔 emoji suffix differing across charset configurations), or the predicate receives a wrapped/processed peerVersion that doesn't exactly equal the constant. Switched to defensive prefix matching (startsWith("ABSENT") / startsWith("ALLOWVANILLA")) — the textual prefix is invariant across Forge versions even when the emoji suffix isn't.

Also refreshes the README to point at the latest jar, document the public Forge event API as the canonical 1.2.0+ scripting hook, and append the four new Apotheosis perks to the integrations table. Extends docs/SMOKE_TESTS.md with a new "1.2.x verification" section (13 test rows) covering all 1.2.x changes: new perks, event API, tooltip wrap, bulk-level, HUD overlay layers, integration master toggles, and the log-noise fixes.

Fixed

  • ServerNetworking.acceptsVersion WARN spam still present after 1.2.1. Replaced NetworkRegistry.ABSENT.equals(...) / ACCEPTVANILLA.equals(...) with peerVersion.startsWith("ABSENT") / startsWith("ALLOWVANILLA"). Defensive against the constant-value drift that prevented the 1.2.1 fix from matching at runtime — confirmed by retesting latest.log after the 1.2.1 install, which still showed the WARN firing every 5 seconds with peer reports ABSENT ?.

Changed

  • README.md installation jar reference 1.1.0 → 1.2.2; appended a milestone sentence describing 1.2.x feature highlights (Forge event API, four new Apotheosis perks, eight integration toggles, tooltip word-wrap, named-layer HUD overlays, bulk-level passives) and a brief mention of the 1.2.1 + 1.2.2 hotfixes.
  • README.md KubeJS scripting section extended to reference the public Forge event API (SkillLevelUpEvent, PassiveLevelUpEvent, PerkToggleEvent.Pre/Post, TitleEarnedEvent) as the canonical 1.2.0+ hook, with a pointer to docs/API_EVENTS.md. Legacy SKILL_LEVELUP reflection bridge documented as deprecated.
  • README.md Apotheosis integration row updated with the four 1.2.0-shipped perks (Apothic Apprentice, Gem-Threaded Armor, Spellsocket, Resonant Affixes).
  • docs/SMOKE_TESTS.md: new "Section 6 — 1.2.x verification" with 13 test rows covering every 1.2.x change.

Notes

  • Save-compatible with 1.2.x and 1.1.x. No NBT, no PROTOCOL_VERSION, no config, no perk behavior changes — pure log noise + documentation.
  • Tested: ./gradlew build passes; on the user's CurseForge instance (no Cataclysm, no BetterCombat, no PointBlank installed) the 1.2.2 install over the 1.2.1 install should show zero Runic Skills WARN/ERROR lines in a 60s post-spawn idle window.

[1.2.1] - 2026-05-14

Log-noise hotfix. Four issues surfaced by the 1.2.0 post-release smoke test on a real CurseForge instance (latest.log review across the 1.2.0 session and four 1.1.0 archives for baseline comparison). Zero functional changes — no NBT, no PROTOCOL_VERSION, no perk behavior. Drop-in over 1.2.0.

Fixed

  • ServerNetworking.acceptsVersion log spam in single-player. The 1.2.0 protocol-mismatch WARN fired every ~5 seconds against Forge's periodic channel-acceptance probes (which pass NetworkRegistry.ABSENT and NetworkRegistry.ACCEPTVANILLA sentinel strings on LAN advertising / ping handlers). Now filters those two sentinels out before logging, so the WARN only fires on a real peer reporting a mismatched version string — which is the case server operators actually want to see. Regression introduced in 1.2.0 Phase C4.
  • EntityKilledCondition / EntityKilledByCondition ERROR-spam on every title-tick. The title-check tick handler (TickEventHandler.onPlayerTickLow, every 200 ticks = 10s) evaluates each title's conditions for each online player. Default title configs reference Cataclysm and Ice and Fire entities; modpacks without those mods saw 2-12 ERROR lines per minute steady state per player. Now: warn-once per unique missing entity name per JVM session via a ConcurrentHashMap.newKeySet() guard, demoted from ERROR to WARN with clearer wording ("Title condition references unknown entity '<id>'. The title will never unlock until that entity's mod is installed."). Pre-existing bug, finally addressed.
  • getLatestVersion 404 logged a full stack trace as WARN at every mod-load. The catch-all Exception handler printed the full FileNotFoundException for the routine "VERSION file not yet published on GitHub" case. Now catches FileNotFoundException, SocketTimeoutException, and UnknownHostException separately and demotes to a single DEBUG line; the generic catch-all retains WARN+stack for genuinely unexpected failures. Pre-existing.
  • MixTargetFinder and MixGunItem Mixin-target-not-found WARN at startup when BetterCombat / PointBlank are absent. The mixins correctly compile inert (no transformations are applied), but Mixin's class loader emits a WARN when it can't find the target class. Added @Pseudo to both — Mixin now silently skips the target-load attempt for these inherently optional-mod-targeting mixins. Pre-existing.

Notes

  • Save-compatible with 1.2.0; no schema, protocol, or config changes.
  • Tested: ./gradlew build passes. Runtime smoke against the user's local CurseForge instance (no Cataclysm, no BetterCombat, no PointBlank installed) should show zero spurious WARN/ERROR lines from Runic Skills over a 60s idle window after spawn — a sharp contrast from the 14+ WARN and 30+ ERROR lines in the 1.2.0 latest.log over the same window.

[1.2.0] - 2026-05-14

Balanced content + quality release. Ships a public Forge event API (SkillLevelUpEvent, PassiveLevelUpEvent, PerkToggleEvent.Pre/Post, TitleEarnedEvent) — external Java mods and KubeJS can now hook level-ups and perk toggles without reflection. Adds 8 per-integration master toggles so pack authors can soft-disable any major integration without removing the dep mod. Adds 4 deferred-backlog perks (Apothic Apprentice, Gem-Threaded Armor, Spellsocket, Resonant Affixes). Tooltip width-clamp at GUI scale 4. HUD overlays moved to named layers so resource packs can relocate them. Bulk-level passives via Shift/Ctrl/Alt-click. Translated protocol-mismatch log line for server ops. CI workflow added. Save-compatible with 1.1.0.

Added — Public Forge event API (since 1.2.0)

  • SkillLevelUpEvent — fired on the Forge bus from SkillLevelUpSP.handle after validation succeeds, before capability mutation and the client sync packet. @Cancelable; cancelling aborts the level-up cleanly without consuming XP. Extends PlayerEvent. Fields: Skill skill, int oldLevel, int newLevel.
  • PassiveLevelUpEvent — fired from both PassiveLevelUpSP.handle and PassiveLevelDownSP.handle. Subscribers wanting only the level-up direction should filter newLevel > oldLevel. @Cancelable. Fields: Passive passive, int oldLevel, int newLevel.
  • PerkToggleEvent.Pre / .Post — fired around TogglePerkSP.handle. Pre fires after the built-in validation chain succeeds, before any state mutation; cancelling aborts the toggle and resyncs the client. Post fires after rank/cooldown writes and is non-cancelable. Both have int oldRank, int newRank, boolean wasEnabled, boolean isEnabled.
  • TitleEarnedEvent — fired from Title.setRequirement whenever unlockTitle flips false→true. Non-cancelable. Fields: Title title.

All four events are documented as a public API and will be maintained across minor versions. KubeJS hooks them natively via its Forge-event bridge — onForgeEvent("net.minecraftforge.event.entity.player.PlayerEvent$SkillLevelUpEvent", event => ...) works out of the box. Legacy KubeJS scripts using the older SkillLevelUpEventJS surface still work unchanged through the KubeJSIntegration shim (which is now marked @Deprecated(forRemoval = true); removal is scheduled for a future major).

Added — Perks (4)

Four perks from the 1.1.0 "Skipped" backlog land this release. The remaining five and the eleven Phase-3 capstones are either dropped permanently (most lack a public API in their upstream mod) or deferred to 1.3.0+ (see below).

  • Apothic Apprentice (Fortune, Apotheosis) — higher-tier socket bonus. +N effective sockets on top of Socket Virtuoso's bonus. Trivial counterpart to the existing perk; both stack additively when enabled. Default requiredLevel = 26, bonus = 2 sockets.
  • Gem-Threaded Armor (Endurance, Apotheosis) — flat ARMOR per equipped socket. Hook LivingEquipmentChangeEvent; iterate dev.shadowsoffire.apotheosis.adventure.affix.socket.SocketHelper.getSockets across all equipment slots; apply a transient ADDITION modifier on Attributes.ARMOR keyed by a fixed UUID. Reconciles on equipment change, not per tick. Default requiredLevel = 20, +0.5 armor per socket.
  • Spellsocket (Magic, Apotheosis + ISS) — ModifySpellLevelEvent adds +1 effective spell level per socketsPerLevel equipped sockets, capped at maxBonus. Same iteration pattern as Affix Focus. Default requiredLevel = 22, 3 sockets per +1 level, max +3.
  • Resonant Affixes (Magic, Apotheosis + ISS) — ISS SpellDamageEvent multiplies outgoing spell damage by 1 + (rare+ affix count × percent). Mirrors Affix Affinity's iteration pattern but on spell damage rather than melee. Default requiredLevel = 24, +3% per Rare-or-better item.

All four config keys live in HandlerCommonConfig under the apothic_attributes group with corresponding *RequiredLevel and magnitude knobs. Lang keys added to en_us.json; other locales fall back to English.

Added — Per-integration master toggles (8 booleans)

New enable<Mod>Integration booleans in HandlerCommonConfig under the integrations group, default true. When false, the integration class is never registered with the Forge event bus — every event handler in the integration is inert. Perks belonging to the integration remain in the registry (so save data is stable across toggle flips). Synced through CommonConfigSyncCP so the client can render UI honestly. The existing lock-item toggles (*EnableLockItems) remain a finer-grained subset that gates only the lock-item generation.

Coverage: Spartan Weaponry, Blood Magic, Ice and Fire, Iron's Spells, Ars Nouveau, Apotheosis, Botania, Jewelcraft.

Added — Tooltip word-wrap helper

TooltipWrap.wrap(List<Component>, int maxWidthPx) clamps long perk/passive tooltip lines via Minecraft.getInstance().font.split(...). Applied at the end of PerkTooltip.tooltip() and PassiveTooltip.tooltip() with a 200px clamp — eliminates offscreen tooltip overflow at GUI scale 4 / 4K. Lines that already fit pass through unchanged so translation keys are preserved in the common case.

Added — Bulk-level passives

Shift-click a passive ± button to apply ±5; Ctrl-click ±10; Alt-click clears (or maxes) the passive subject to skill-level gates. Each click sends N PassiveLevelUpSP / PassiveLevelDownSP packets; the server validates each independently and silently rejects increments past the cap. Skill level-up still uses single-click (server rate-limit on SkillLevelUpSP makes bulk-level less useful there). Implemented in RunicSkillsScreen.bulkClickAmount.

Added — CI workflow

.github/workflows/build.yml runs ./gradlew build on push and pull request — compiles, runs checkSidedImports, reobfuscates, and assembles the jar on a clean Ubuntu image. Catches the dedicated-server class-load regression class on a clean classpath (no YACL, no L2Tabs, no optional mods); 1.0.0 (Legendary Tabs) and 1.1.0 (YACL) shipped that bug separately, so a no-optional-mods CI build is the highest-value smoke gate. Build artifact (jar) uploaded for 7 days.

Changed

  • HUD overlays migrated to RegisterGuiOverlaysEvent. OverlaySkillGui and OverlayTitleGui previously piggy-backed on CustomizeGuiOverlayEvent.DebugText (the F3 overlay event — worked but conceptually wrong). Now registered as named layers runicskills:skill_overlay and runicskills:title_overlay above the hotbar layer. Same render code, same visual position; resource packs can now relocate them via the standard above/below overlay APIs. Tick subscribers remain on the Forge bus for state updates.
  • KubeJSIntegration deprecated in favor of the new Forge events. postLevelUpEvent is now @Deprecated(forRemoval = true) — its reflective fast-path cache stays for back-compat with existing pack scripts; new scripts should subscribe to SkillLevelUpEvent on the Forge bus directly. Migration documented in docs/API_EVENTS.md.
  • @Nullable annotations on RegistryPerks.getPerk, RegistryPassives.getPassive, RegistrySkills.getSkill (returns null for unknown registry names). IntelliJ + IDEA plugin now surfaces unchecked dereferences as warnings — closes the residual P1 #5 leads from the 0.9.3 audit.
  • Protocol-mismatch log line (1.2.0). ServerNetworking.acceptsVersion now logs a clear warning when a connecting peer reports a different PROTOCOL_VERSION — server ops can diagnose mismatches from the log instead of debugging a generic Forge disconnect. The player-facing disconnect message remains Forge's generic "channel mismatch" because the kick is initiated by Forge's negotiation layer; full message translation would require an invasive negotiation-layer mixin not worth the risk.

Fixed

  • (No new bug fixes this release — 1.1.0 was the catch-up consolidation.)

Removed from roadmap (won't ship)

After triaging each deferred design-doc perk against the actual upstream API surface, the following are dropped permanently because they require invasive mixins into private dispatch paths or assume APIs that don't exist:

  • Pack Caller — ISS SummonManager is private and event-less.
  • Eldritch Apprentice — Eldritch research XP lives in a private capability with no extraction hook.
  • Spawner Mage / Spawner Sanctuary — ISS summons ≠ vanilla SpawnerBlockEntity; the perks conflate two mechanics. Apotheosis has no spawner event either.
  • Split-Caster — ISS 3.15 has no spell-casting-split event; pervasive mixin into spell dispatch is too invasive for the value.
  • Glyph-Imbued Gem — gem metadata is immutable post-socketing; no post-apply hook.
  • Enchanter's Insight — design assumes Ars uses enchantments (it uses glyphs); conceptual mismatch.
  • Enchanter-Arms — no Apotheosis event for enchantment-apply success.
  • Apparatus Synergy — Apotheosis Loot Apparatus is datapack-only; no hook.
  • Mythical Scribe — Ars has no glyph-discovery XP event.

Deferred to 1.3.0+

Doable but blocked on implementation budget or scoped to wait until the new 1.2.0 event API has bedded in:

  • Sourcelink Affix — Ars SpellResolveEvent.Post + affix-name match for source refund. Drops to lower priority because it depends on Apotheosis affix-name stability.
  • Adaptive Caster — per-player school-cast history map with decay; signal-state pattern needs more design.
  • Ars Familiar AttunementEntityJoinLevelEvent filtered by IFamiliar. Needs verification against Ars 4.12.x familiar-spawn paths.
  • Botania Mana OverflowTickEvent.PlayerTickEvent + nearby-pool drain on mana-cap. Counterpart to Tidewoven/Resonance.
  • ISS Cascade Attunement — N-casts-of-X-unlock-Y-for-30s transient state map.
  • Datapack-driven titles — Forge's IForgeRegistry<Title> is frozen after RegistryEvent; full datapack support requires a parallel runtime title store with its own evaluation/sync plumbing. The existing HandlerTitlesConfig.titleList YACL config already lets pack authors author titles, just without vanilla /reload hot-swap. Targeted for 1.3.0.
  • Gemsmith, Lucky Loot, Library Dedication, Ars Scholar, Glyphsmith, Bookwyrm's Apprentice — all require reflection into private upstream state; group into a future "Ars / Apotheosis deep-integration" pass.
  • Ritualist, Dead King's Debt, Ritualized Reforge, Arcane Syncretism — multi-event coordination across mods; benefit from the 1.2.0 event API once external mods start adopting it.
  • GameTest scaffolding — initial gradle wiring and test-class scaffold are designed but the structure-file setup and per-class loader plumbing want a focused release. CI build of the main jar lands now; full GameTest run in CI lands later.

Notes

  • Save-compatible with 1.1.0. No NBT schema changes; the four new perk ranks default to 0 on first load. The 8 new config booleans default to true (preserve existing behavior). PROTOCOL_VERSION stays at 5 — no wire-format breaking changes. The 1.1.0→1.2.0 upgrade is drop-in.
  • Tested: ./gradlew clean build passes (compileJava + checkSidedImports + reobf + jar assembly). Runtime smoke testing per docs/SMOKE_TESTS.md is required before CurseForge upload — primary regression rows: client tooltip render at GUI scale 4 (no overflow), Apothic Apprentice + Socket Virtuoso stacking on a rare gem-socketed item, Spellsocket bonus visible in F3 spell-level overlay, Shift-click bulk-level a passive from 0 to 5 in one click.