# Aeshnidae.Awakens

The repeatable enlightenment. At its level cap a character may **awaken**: back to
level 1 with skills, attributes, skill credits and unassigned experience reset as
retail enlightenment resets them (`ResetSkillsAndAttributes`; off is the original
soft awakening that kept them), keeping society rank, luminance, luminance auras,
titles, Radiance and mastery raises. Each awakening
raises the tier of the character's personal dungeon copies by one: +5% harder,
+5% richer (`Aeshnidae.InstancesNoDat`, whose tier is the awakening count -
enlightenments do not count, `CountEnlightenments` false) - and raises the character's **level cap by one**: 275 for a
character that has never awakened, 276 after the first, 277 after the second. The
next awakening needs the new cap. Retail enlightenment is untouched and runs
beside it.

```
/awaken                  what it is, whether you may, how many times you have
/awaken confirm          do it, within a minute of /awaken; no undoing
/awaken-set <player> <n> admin: set the count without awakening (testing)
/awaken-reload           admin: re-read Settings.json
```

## What an awakening does, exactly

With the reset on: retail enlightenment's own `DequipAllItems`, `RemoveAttributes`
and `RemoveSkills` (every skill untrained with its credits refunded, credits back to
heritage plus quest credits, attribute and vital ranks to zero, unassigned experience
to zero), then `TotalExperience` to 0 and `Level` to 1 as `RemoveLevel` does. Not
called: `RemoveSociety`, `RemoveLuminance`, `RemoveAetheria` - those are what make
it an awakening and not an enlightenment. With the reset off only the level goes,
and only items with a **level** wield requirement go to the pack
(`DequipLevelGatedItems`). The count goes up by one. A burst and a chime, and a
line to the server.

**The count** is `PropertyInt 9040` on the character - an id ACE's enum does not
define, so it is never sent to the client, never shown, and rides in the biota:
saved and backed up with the character, ignored by retail enlightenment (which
resets only the properties it names). InstancesNoDat and the Leaderboard read the
same id; the mods share a number and nothing else.

**The credit rule.** `Player.CheckForLevelup` pays skill credits at every credit level
as a character climbs. With the reset the credits were taken, so the climb pays them
again, as retail does. With the reset off the character kept every credit from the
first climb, and the take-over in `Patches.cs` pays none (`NoLevelCreditsAfterAwakening`).

**The cap.** ACE has one level cap for everyone - the length of the client's XP
table, `Player.GetMaxLevel()`. `LevelCap.cs` grows that table in memory past
retail (`LevelCapHeadroom` levels, on the retail curve, no skill credits on the new
levels) so the server has a total for 276 and up, and `Patches.cs` takes over the
two methods that climb it - `UpdateXpAndLevel` and `CheckForLevelup`, ACE's bodies
with the character's own cap where they read the table's end. `GetMaxLevel()`
itself is not patched: it is two instructions the JIT inlines, and every remaining
caller is fine seeing the table's end. Past 275 the client has no XP table of its
own, so its "XP to next level" line goes blank; the level, the total and the
messages are the server's and correct.

**What the level does elsewhere, unchanged on purpose:** portals with a level
requirement refuse a level-1 character, quests with level gates gate, level-based
loot tiers apply. That is the climb; the character is climbing it with a level-275
skill set, which is the point.

## Resolve (2026-10-13)

The other dial this mod owns, and a separate ladder from the awakening count.

**Resolve is the danger a character sets their own challenge runs to** - the new dungeon
type being designed: random but difficulty-matched monsters, no corpses, a boss and a
chest, get in and get out. It is bought as **seals**:

1. **Buy Seals of Resolve** from Ismene Vale, Keeper of Seals, who is a plain ACE **vendor**:
   use her, a shop window opens, buy as many as you like at a flat price. No command.
2. **Press one onto your Scroll of Resolve** - ACE's own use-item-on-item action. The seals
   the next step needs burn away together and your Resolve rises by one, at once. A short
   stack is refused by name and nothing is taken.
3. **An Eraser of Resolve** is bought and used the same way, takes one step back at once, and
   returns **nothing**.

```
/resolve                       your Resolve, what the next step takes in seals, how many you carry
/resolve scroll               ask the Keeper you are standing at for the scroll (free, replaceable)
/resolve buy | confirm        retired; answers with where the shop is
resolve-set <player> <n>      admin, console-safe, works offline: set it without charging
resolve-set <player> seal|eraser       admin: take one step up or down, nothing consumed
resolve-status [steps [n] | apply <carried> <applied> [eraser] | vendor | scroll <player>]   admin, console-safe
resolve-reload                admin: re-read Settings.json (the same restart as awaken-reload)
```

**What it affects today: nothing**, and the readout says so in those words. The challenge
dungeons do not exist yet. Resolve deliberately changes nothing about any dungeon that
does: the awakened hub ladder and every personal copy still scale with the character's
**awakenings**, +5% a tier, exactly as before. This mod does not touch
`Aeshnidae.InstancesNoDat`'s tier and nothing here is wired into it - a character with
Resolve 50 sees precisely the dungeons they saw with Resolve 0. The dial was built first
because the number is the hard half: it has to be bought, be permanent, survive a logout,
resist being farmed cheaply, and be readable by a sibling mod.

**State on the character, not an item.** `PropertyInt 9053` is the Resolve in force - an id
ACE's enum does not define, so it is never sent to a client and simply rides in the biota,
saved and backed up with the character and left alone by retail enlightenment and by the
awakening reset. Progression that lives in an item is losable, and "my difficulty got
deleted" is worse than any convenience an item would buy.

### Where the escalation went (2026-10-13, evening)

It used to be the **price**. `/resolve buy seal` quoted 2,000,000 x 1.5 per seal already paid
for, took pyreals out of the pack and minted a seal; `PropertyInt 9054` counted seals paid for
so the quote could not be dodged by buying ten before applying any.

Ismene is a **vendor** now, and a vendor's price is one fixed number per item. So the price
went flat and the escalation moved to **how many seals a step takes** - deliberately the same
curve:

```
before: step k cost      2,000,000 x 1.5^(k-1)  pyreals for ONE seal
after:  step k takes             1 x 1.5^(k-1)  seals at 2,000,000 pyreals each
```

which is the same money, because the base price did not change. Reaching any Resolve costs
what it always did, to within the rounding of a seal count to a whole seal:

| Resolve | seals in all | now | before | difference |
| --- | --- | --- | --- | --- |
| 1 | 1 | 2,000,000 | 2,000,000 | 0.00% |
| 2 | 3 | 6,000,000 | 5,000,000 | +20.00% |
| 5 | 13 | 26,000,000 | 26,400,000 | -1.52% |
| 10 | 113 | 226,000,000 | 226,800,000 | -0.35% |
| 20 | 6,649 | 13,298,000,000 | 13,297,200,000 | +0.01% |

Everything past step 12 is inside 0.03%. The one real divergence is step 2, where a whole
seal is a coarse unit: 1.5 rounds to 2, so the second step is 4,000,000 instead of 3,000,000.
Halving the seal to 1,000,000 and making step 1 two seals would halve that granularity - two
settings, no ledger - and is the lever to pull if the bottom of the ladder ever matters.

Per step, from `resolve-status steps`: 1, 2, 2, 3, 5, 8, 11, 17, 26, 38, 58, 86, 130, 195,
292, 438, 657, 985, 1478, 2217.

**Two walls now instead of one.** The money, as before - and the **pack**, because a step's
seals have to be carried at once. A stack is 250, so step 15's 292 seals are two slots and
step 20's 2,217 are nine; somewhere past step 27 no pack can hold a step at all. Burden is
not a wall (2,217 seals is 11,085 against a capacity of 150 x Strength x 3). The arithmetic
saturates at `Seals.Uncountable` (`int.MaxValue / 4`) and is **refused by name**: step 51 is
the first whose seal count passes it, step 0 and below are refused, a growth below 1.0 is read
as flat, and a count is never below one seal. `Aeshnidae.SkillMastery` minted 7.19e18 Radiance
on 2026-09-22 by not guarding a compounding curve, and with no cap on Resolve this curve is
the only limit there is.

**9054 is retired.** It existed only to price an escalating purchase, and a flat price cannot
be dodged by buying early because buying early costs exactly the same. It is written nowhere
and read nowhere; `Resolve.RetiredPaidProperty` exists only so `Resolve.RetirePaidCount` can
delete the row from a character this mod writes, for a biota restored from before the
retirement. Both shard databases, live and staging, had **zero** rows of it when it was
retired, so there was nothing to migrate. `resolve-set <player> paid <n>` answers with that
instead of setting anything.

### The prices, and which copy is the truth

The seal is **8 Trade Notes (250,000) = 2,000,000 pyreals**, flat. The eraser is **4 Trade
Notes (250,000) = 1,000,000**, flat - half a seal, which is what the first eraser cost before.

The **world database is the truth**: the price is the seal weenie's `Value` read through the
vendor's `SellPrice` and `AlternateCurrency`, exactly as `Vendor.GetSellCost` does it.
`SealPyreals` and `EraserPyreals` in the settings are this mod's copy, used only to write the
readout when the database cannot be read; `Seals.PriceAt` derives the real one and
`resolve-status vendor` prints both and says when they disagree, because a readout quoting a
price the shop does not charge is the drift this repo keeps getting bitten by.

**Why trade notes and not coin.** ACE takes a vendor payment out of `Player.CoinValue`, which
counts pyreal **coin** only - a trade note is a `Stackable` and does not count. A 2,000,000
price in coin would mean eighty stacks of 25,000 in the pack for one seal. Retail's own
expensive vendors solve it the same way: the Gauntlet ticket vendors (52856-52858), Creed
(40591) and Ianto/Iqbal/Qing/Ciriaco (42428-42431) all take `AlternateCurrency` 20630 and
nothing else. It also removes an overflow - ACE's `totalPrice` is a `uint`, and 2,148 seals at
2,000,000 in coin would have wrapped it.

**Why the eraser returns nothing.** A step of Resolve is a decision about how you play, and
the seals you spent bought the danger you have been running at. A refund would make Resolve a
rental: park at 25 for one run, erase your way back down, and the ladder means nothing. Going
back is instead made cheap and flat - always cheaper than going forward, and at deep Resolve
very much cheaper than the step it undoes - so changing your mind is affordable without being
free. It cannot be a pump in either direction precisely because nothing comes back: erasing
and re-climbing is a straight loss. It is said in as many words on the eraser, in the shop
window's greeting and in the scroll's readout, so nobody buys one expecting money back.

### The weenies (ledgers `2026-10-13_001` and `2026-10-13_005`)

| | |
| --- | --- |
| `3000137` | **Scroll of Resolve** - a parchment shaped on retail's 365, attuned and bonded, two pages of 1,000 characters. The readout, and the surface a seal is pressed onto. **Not for sale** - it is free from `/resolve scroll`, and a Value-0 item in a shop would price at one trade note. |
| `3000138` | **Seal of Resolve** - shaped on our own 3000126 Ember Pitch, icon of retail's Pyreal Scarab. Stackable to 250, **attuned**, bonded, `IsSellable` false, `TargetType` Writable, `Value` 8. |
| `3000139` | **Eraser of Resolve** - the same, with the Lead Scarab's icon and `Value` 4. |
| `3000143` | **Ismene Vale, Keeper of Seals** - WeenieType 12 **Vendor**, modelled on retail's `ace52856-gauntletticketvendor`: `MerchandiseItemTypes` 0, min 0, max 100,000, `BuyPrice` 1, `SellPrice` 1, `AlternateCurrency` 20630, `PlayerKillerStatus` 16 (RubberGlue, which the enum also calls Vendor), `DealMagicalItems` true, and two `create_list` rows at `destination_Type` 4 with `stack_Size` -1 (unlimited). Her name, title, appearance, attributes, kit, UseRadius and idle twitches are unchanged, and her position is not in any ledger - `challenge_hall_settings.py` places her by wcid. |

**She buys nothing.** `MerchandiseItemTypes` 0 is the mechanism: ACE requires the property to
exist (a vendor missing any of the five is logged `Vendor has been disabled` and then silently
does nothing when used) and `Player.VerifySellItems` refuses every item whose ItemType is
outside the mask - with 0, that is every item. Thirty-nine retail weenies use 0 for exactly
this, every quartermaster and ticket vendor among them. `AllowGive` stays FALSE and she has no
Give emote, so a handed item gets "she doesn't want it" and goes straight back. And the seal
and the eraser are `IsSellable` false, so no **other** vendor buys a 2,000,000-value token off
a player either.

Her three spoken lines moved from the Use emote to a Vendor/Open emote (`EmoteCategory` 2,
`vendor_Type` 1): `Vendor.ActOnUse` overrides the creature's, so a Use emote on a vendor never
fires again.

The seals are **attuned** so they cannot be bought on one character and handed to another -
the ladder is per character, and a shared pool of seals would let two characters fund one
climb. Being a stack also keeps `Aeshnidae.AwakenedRewards`' attuned-item scaling off a
purchase token (it skips anything whose `MaxStackSize` is above 1); the scroll, which is not a
stack, is pre-stamped with that mod's own marker (9042) on minting for the same reason, so it
is never renamed " (Awakened N)".

**Using one on the scroll is a Harmony prefix, not a recipe.** ACE's source-on-target grammar
ends in `RecipeManager.UseObjectOnTarget`, which looks the pair up in `cook_book` and runs a
`recipe`. A real recipe/cook_book pair was the obvious route and was rejected: a recipe's mods
operate on the source and target **items**, and what has to change is a mod-owned PropertyInt
on the **player**, with our own refusals and log line; a recipe cannot take a **variable**
number of sources off a stack, which is now the whole mechanism; the recipe engine brings a
difficulty roll that could **fail** and destroy a purchase; a `cook_book` row would hard-code
the seal wcid against the scroll wcid in the world database, so the pairing would live in two
places; and the house rule is that every customisation is a Harmony mod. The client still does
all of ACE's work - the use-on-target action, the `TargetType` compatibility check, the
walk-to - and the prefix replaces only what happens at the end. `UseObjectOnTarget` is a large
method with a real body that the JIT cannot inline away, and `resolve-status` asserts both
hooks are applied rather than assuming it.

**The apply path cannot part-charge.** The seals carried are counted before anything is
consumed and a short stack is refused outright; the consume is then checked by re-counting,
because ACE's consume-by-wcid returns true even when it runs out of stacks to take from. If
the two ever disagreed the step would **not** be granted and the player would be told, because
granting it would be worse than refusing it.

**A second kind of seal is data, not code.** Everything about a kind - its label, what it
sets, its property id, its two weenies, its seals-per-step curve, its maximum and the two
display prices - is one entry in the `Seals` list. Adding "denser rosters", "no recall" or "no
vendor healing" costs: one settings entry, two weenies in a ledger (with the vendor rows of
`2026-10-13_005` copied onto whoever sells them), one free property id from the repo's
allocation, and a line in the Readme. No new code, no rebuild.

## Settings

`ResetSkillsAndAttributes` (true), `LevelsPerAwakening` (1), `LevelCapHeadroom` (100 - room for a hundred awakenings),
`MaxAwakenings` (0 = uncapped), `DequipLevelGatedItems`, `NoLevelCreditsAfterAwakening`,
`ConfirmSeconds` (60), `Effects`, `BroadcastToServer`.

Resolve: `ResolveEnabled` (true); `Seals`, a list of kinds, one today - `Key` (resolve),
`Label` (Resolve), `What` (the line the readout quotes), `PropertyId` (**9053, and it must
stay 9053** - that is the contract), `SealWcid` (3000138), `EraserWcid` (3000139), `Max`
(**0 = no maximum**, the design: the seals-per-step curve is the wall), `SealsForFirstStep`
(1), `SealsPerStepGrowth` (1.5), `SealPyreals` (2,000,000 - display only), `EraserPyreals`
(1,000,000 - display only); then `ResolveScrollWcid` (3000137), `ResolveSellerRequired` (true -
for the scroll handout only), `ResolveSellerWcids` (`[3000143]` - the Keeper of Seals in the
Challenge Hall), `ResolveSellerRange` (10 m), `ResolveSellerDescription` (how she is named to a
player who is not standing at her). Every one is documented at its declaration.

Retired with the vendor change, and dropped from the deployed files: `PaidPropertyId`,
`PerSeal`, `FirstSealPyreals`, `SealPriceGrowth`, `FirstEraserPyreals`, `EraserPriceGrowth`,
`ResolveConfirmSeconds`, `MaxPerPurchase`.

| File | |
| --- | --- |
| `Awakening.cs` | The refusals, the confirmation, the awakening, the count. It does nothing to Resolve - the two ladders do not meet. |
| `LevelCap.cs` | The per-character cap and the grown XP table. |
| `Patches.cs` | The level-up and XP methods, with the cap and the credit rule. |
| `Resolve.cs` | The Resolve contract: the property, the sentinel, the retired count, the readout, and what a consuming mod binds to. |
| `Seals.cs` | The seal kinds, the seals-per-step curve, the apply guard, applying a seal or an eraser, what the shop really charges - and why that is a prefix and not a recipe. |
| `Scroll.cs` | The scroll, the Keeper's handout, and the two prefixes: `Book.ActOnUse` and `RecipeManager.UseObjectOnTarget`. |
| `Commands.cs` | `/awaken`, `/awaken-set`, `/awaken-reload`, `/resolve`, `resolve-set`, `resolve-status`, `resolve-reload`. |
| `Settings.cs` | The terms. |

`Pyreals.cs` is gone: the mod no longer handles money at all. ACE's vendor does.

## Reading a character's Resolve from another mod

The challenge-run build is the consumer. Two routes, both supported:

1. **Read the property.** `player.GetProperty((PropertyInt)9053) ?? 0`, clamped at 0. No
   binding, no reflection, nothing to break on a reload - exactly how InstancesNoDat reads
   9040 today. Take this one unless you need the cost curve.
2. **Bind `Resolve.Of` reflectively**, the sibling-mod pattern `Aeshnidae.AwakenedRewards`
   and `Aeshnidae.Towns` use (their `Instances.cs` explains it at length). `Resolve.Of`,
   `Resolve.SealsForStep` and `Resolve.PriceOfStep` are kept stable for that purpose and take
   only host types (ACE's `IPlayer`) and primitives, so they bind across the mod boundary
   and work for an offline character as well as an online one. Re-bind whenever the
   assembly differs.

`Resolve.PriceOfStep` **changed meaning on 2026-10-13 evening**: it used to be the price of
one seal read off the paid count, and it is now the seals that step takes times the flat seal
price - the same money for the same step, but a different question. `Resolve.SealsForStep` is
the one to read for the curve itself. Nothing had bound either when it changed.

Do **not** write 9053 from another mod. It is bought, and this mod is where it is applied.
