Skip to content

Aptitude & Insight Point

Aptitude is the player's experience reservoir, and Insight Points are the spendable skill points. Together they form Epiphany's resource system.

INFO

This page is aimed at datapack authors, explaining aptitude source configuration and the Insight Point acquisition formula. For registering new aptitude behaviors (third-party mod extensions), see Registering a New Aptitude Source.

Core Flow

File Location

Aptitude mappings are defined as per-behavior datapack JSON files:

data/<namespace>/epiphany/aptitude/<behavior>.json
  • Each file represents one behavior type (e.g., kill_entity, mine_block, experience_level_up)
  • Behavior ID = <namespace>:<filename> (parent directory paths are not included)
    • data/mymod/epiphany/aptitude/fishing.jsonmymod:fishing
  • Behavior IDs are strictly tied to listener requirements — the 13 built-in behaviors have fixed filenames (see table below). Other IDs only take effect when a third-party mod registers a corresponding listener.

Full Example

jsonc
{
    "default": 3,                              // optional, defaults to 0
    "specials": [                              // optional, defaults to empty
        {
            "target": "minecraft:zombie",      // required; plain id or "#tag"
            "reward": 5,                       // optional; defaults to `default`
            "first_reward": 1000               // optional; granted once per player (persisted)
        },
        {
            "target": "minecraft:wither",
            "reward": 100,
            "first_reward": 500
        }
    ],
    "exclude": [                               // optional, defaults to empty
        "#epiphany:friendly",                  // if matched, skip this grant entirely
        "minecraft:villager"                   // plain id or "#tag"
    ]
}

Minimal form (only a default reward):

jsonc
{ "default": 2 }   // e.g., experience_level_up.json —+2 aptitude per level-up

Field Reference

default (optional)

  • Type: long
  • Default: 0
  • The aptitude granted when the trigger target is neither in specials nor in exclude

TIP

If default = 0 and no specials match, the behavior grants no aptitude at all.

specials (optional)

  • Type: list of SpecialEntry
  • Default: empty
  • Per-target reward rules that override the default

Each SpecialEntry has three fields:

FieldTypeRequiredDescription
targetStringTarget reference — plain id ("minecraft:zombie") or tag reference ("#minecraft:undead")
rewardlong (optional)Aptitude reward for this target; falls back to default when omitted
first_rewardlong (optional)Extra reward granted only once per player, stacked on top of reward; tracking state is persisted in player data

first_reward Usage

first_reward is an extra bonus for "the first time a player does something." Typical use cases:

  • First boss kill: { "target": "minecraft:wither", "reward": 100, "first_reward": 500 } — the first wither kill grants 600 (100 + 500); subsequent kills grant only 100
  • First time entering a rare biome: same logic

Each player's "claimed list" is saved in their player data and persists across death, respawn, and dimension changes.

exclude (optional)

  • Type: list of String
  • Default: empty
  • Blacklist: if matched, the grant is completely skipped (bypasses both specials and default evaluation)
  • Each entry is the same format as target — plain id or "#tag"

exclude has "short-circuit" semantics

exclude is evaluated before specials: if any exclude entry matches the target, the entire grant immediately terminates, even if a special also matches that target. In other words, exclude has the highest priority.

Use case: "no aptitude for any friendly mobs" —"exclude": ["#epiphany:friendly"] — no need to list each one individually.

Tags & Registry Relationship

Both specials[].target and exclude[] support the "#tag" format (standard Minecraft tag references). However, to correctly resolve a tag, the system needs to know which registry the target belongs to:

  • Built-in behaviors have fixed registries (see table below) — datapack authors don't need to worry about this; just use the correct tag namespace
  • If a behavior has no natural registry (e.g., experience_level_up), the "#tag" format never matches and will fall back to default or grant nothing

Built-in Behaviors (13 types)

Below are the 13 behaviors that Epiphany provides listeners for natively. The filename in your datapack must match the ID below for the listener to find your configuration.

General Behaviors (Vanilla Events)

Behavior IDTriggerTarget FormatRegistry#tag Support
epiphany:kill_entityPlayer kills an entityentity type id (minecraft:zombie)ENTITY_TYPE
epiphany:mine_blockPlayer mines a blockblock id (minecraft:diamond_ore)BLOCK
epiphany:advancement_earnPlayer earns an advancementadvancement id (minecraft:end/kill_dragon)
epiphany:experience_level_upPlayer gains an experience levelnone (placeholder)
epiphany:enter_dimensionPlayer changes dimensiondimension id (minecraft:the_nether)

experience_level_up only uses default

experience_level_up has no natural target (each level-up is just a number change). The listener calls grant once per level, using a placeholder target. So datapacks only need to write:

jsonc
{ "default": 2 }

specials and exclude are ineffective (the target is always epiphany:_).

State-Tracking Behaviors (Require Persistent Comparison)

Behavior IDTriggerTarget FormatRegistry#tag Support
epiphany:enter_biomePlayer's current biome changesbiome idBIOME (from level registry)
epiphany:enter_structurePlayer's set of containing structures changesstructure id or epiphany:none (not in any structure)STRUCTURE (from level registry)

Special Target for enter_structure

When the player is not inside any structure, the listener sends epiphany:none as the target (a sentinel value). You can use this to implement "exploration reward only while inside structures" logic:

jsonc
{
    "default": 1,
    "specials": [
        { "target": "epiphany:none", "reward": 0 }  // no aptitude when not in a structure
    ]
}

Epiphany Internal Behaviors

Behavior IDTriggerTarget Format
epiphany:module_selectedModule selected (ModuleSelectedEvent)module id
epiphany:module_completedModule completed (ModuleCompletedEvent)module id
epiphany:insight_selectedInsight unlocked (InsightSelectedEvent)insight id
epiphany:epiphany_selectedEpiphany activated (EpiphanySelectedEvent)epiphany id

FTB Quests Integration (Soft Dependency)

Behavior IDTriggerTarget FormatNotes
epiphany:ftbq_quest_completeFTB Quests quest completedFTBQ quest hex string idRequires FTB Quests installed
epiphany:ftbq_chapter_completeFTB Quests chapter completedFTBQ chapter hex string idSame as above

When FTB Quests is not loaded, these two listeners will not register (soft dependency isolation), and the corresponding JSONs are simply ignored without causing crashes.

Configuration

The following options are in config/epiphany-common.toml and affect all aptitude acquisition:

Config OptionDefaultDescription
aptitudeGainMultiplier1.0Global multiplier applied to all aptitude granted by datapack behaviors; 0.0 effectively disables datapack sources, 2.0 doubles everything
baseAptitudeCap10Base aptitude required for the first Insight Point
aptitudeCapGrowth1Additional aptitude required for each subsequent Insight Point

Use the multiplier for balancing

You don't need to adjust individual JSON values. If the overall pacing is too fast or too slow, simply change aptitudeGainMultiplier to globally scale — without affecting the relative proportions within your datapack.

Aptitude —Insight Point Formula

When the aptitude bar is full, it auto-converts to 1 Insight Point and resets to zero. The required aptitude increases with the total number of Insight Points earned:

Required=baseAptitudeCap+(totalSpent+insightPoints)×aptitudeCapGrowth\text{Required} = \text{baseAptitudeCap} + (\text{totalSpent} + \text{insightPoints}) \times \text{aptitudeCapGrowth}

Where:

  • totalSpent = total Insight Points the player has historically spent
  • insightPoints = currently available unspent Insight Points
  • Their sum = total Insight Points ever earned = the current tier

Default Formula Examples (baseAptitudeCap=10, aptitudeCapGrowth=1)

Total Insight Points EarnedAptitude Needed for Next Point
010
111
212
515
1020
5060

Adjust the two config parameters to control pacing:

  • Increase baseAptitudeCapslower early game (stretches the beginning)
  • Increase aptitudeCapGrowthsteeper late game (widens the depth)
  • Increase both — massive aptitude requirements throughout

Where Insight Points Go

Insight Points leaving the aptitude system have two spending destinations, both handled uniformly:

Spending ScenarioAmountAffected Fields
Selecting a moduleConfig.moduleSelectCost (default 1)insightPoints--, totalInsightPointsSpent++
Unlocking an insightPer-insight cost field (default 1)Same as above

Next Steps