hv-unified/references/jpx-analysis.md
GaboGG 402db6bb2f Initial commit: HV Unified v0.11.0 structure
- 23 source files in src/ (build via scripts/build.sh)
- Forum-sourced player knowledge in references/
- DESIGN.md with architecture and corrections
- References to existing scripts (Monsterbation, jpx, HV Utils)
2026-07-20 19:30:26 -04:00

689 lines
34 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Deep-Structural Analysis of jpx HentaiVerse Auto-Battler Userscript
**File:** `/home/gabogg/Downloads/jpx20260706.txt`
**Version:** 2026.07.06
**Size:** 270,752 bytes, 6,215 lines
**Language:** Vanilla JavaScript (no framework), single-file userscript
---
## 1. ARCHITECTURE
### 1.1 Initialization Flow
The entire script is a single-file userscript with a declarative bootstrap:
1. **Userscript Header** (lines 1-11): `@run-at document-end`, matches `*.hentaiverse.org/*`, excludes `/equip/*` and `/isekai/equip/*`. No GM_ grants needed — works purely via DOM.
2. **Module Bootstrap** (lines 463-465): Three singleton-style modules are immediately invoked:
- `jpxPanelManager()` — creates/configures the control widget overlay
- `jpxMarket()` — market price fetcher/cacher
- `jpxUtils()` — utility library
Each follows the pattern `function jpxThing() { const ns = jpxThing; if (ns._init) return ns; ... ns._init = true; return ns; }` — single-init guard via `_init` flag on the function object itself.
3. **Entry Point** (line 6215): `initDo()` is called at file's end.
4. **`initDo()`** (line 1242): The router function that:
- Injects CSS via `<style id="jpx">`
- Registers `beforeunload` handler (`storeTmp`) for crash-safe state persistence
- Registers `pointerdown` handler to dismiss multi-select popup panels
- Registers `keydown` handler with throttled `actionManager` (75ms throttle)
- Initializes I18N (`initDoI18n`)
- **Page Detection** via `document.querySelector('#textlog')`:
- **If `#textlog` exists AND `doInitDoBattle` is false** → `initDoBattle()` — we're in a battle
- **If `#riddlemaster` exists** → `riddleRecorder()` — RiddleMaster encounter
- **If no `#textlog` (Lobby)** → reads player info (level, stamina, difficulty, persona, spell damage bonus) from DOM selectors and stores in localStorage
### 1.2 Hooking Into the Game
The script hooks into the game at three levels:
1. **MutationObserver on `#textlog`** (line 1541): A `MutationObserver` watches `log.firstChild` for `childList` changes. Every time new battle log text is added, `preProcessLog()` fires (throttled at 200ms, trailing). This is the primary event loop.
2. **DOM event listeners**:
- `keydown` on `document` (capture phase, line 1255) — for hotkeys and key-bound battle actions
- `pointerdown` on `document` (line 1249) — for dismissing multi-select panels
- `mousemove` on `window` (line 1366) — sets `jpxPanelManager.ready = true`
- `DOMContentLoaded` for `reDoBattle()` on round transitions (line 1369)
- `beforeunload` on `window` (line 1248) — calls `storeTmp()` to persist state
3. **Custom AJAX round advancement** (lines 1623-1716): When `ajaxRound` is enabled, the script intercepts the "Continue" button (`#btcp`) onclick, replaces it with an async XHR-based flow that fetches the next round HTML, handles RiddleMaster encounters inline, and re-initializes the game's `window.Battle` object via `document.dispatchEvent(new Event('DOMContentLoaded'))`.
### 1.3 Page/Context Detection
The script detects its environment via DOM selectors with no URL parsing beyond the `isekaiSuffix` (line 475):
| Condition | State | Action |
|-----------|-------|--------|
| `#textlog` exists + `!doInitDoBattle` | In battle | `initDoBattle()` |
| `#riddlemaster` exists | RiddleMaster | `riddleRecorder()` |
| No `#textlog`, Bazaar AM modify or Battle IW | Lobby (IW equipment screen) | Observe `#equipform` for world level/difficulty |
| No `#textlog`, `s=Battle` query | Lobby (battle selection) | Read level, stamina, persona, spell damage bonus |
| Default | Lobby (other) | No action |
---
## 2. RULE ENGINE
### 2.1 Configuration Structure
Battle configuration is stored in `cfgBattle` (line 13), merged from:
- `defaultCfgBattle` (lines 14-229) — hardcoded defaults for all fighting styles
- `localStorage['jpx_cfgBattle' + isekaiSuffix]` — user overrides
- Merged by `mergeCfg()` (line 5034) with version-aware reset logic
The config schema for each battle mode (`{Style}_{Type}`) has three sections:
- `supports` — array of support actions (healing, buffs, items) evaluated first
- `attacks` — array of offensive actions evaluated second
- `kb_*` (key-bindings) — direct hotkey-triggered action sequences stored as dynamic keys
The supported battle mode keys follow BATTLE_MODES (line 657):
```
Style × Variant: 10 styles × 6 types = 60 possible modes
Styles: OneHanded, 1H_Mage, TwoHanded, 2H_Mage, DualWielding, DW_Mage, NitenIchiryu, NI_Mage, Staff, Unarmed
Types: General, Arena, Encounter, Colosseum, Battle1000, Item, Tower
```
### 2.2 Action Types
Each action is an object with a `type` discriminator and optional `conditions` array:
| Type | Description | Parameters |
|------|-------------|------------|
| `stop` | Halts auto-battle with custom message | `customMessage`, `conditions` |
| `spellSupport` | Casts a supportive spell | `name` (from `SPELLS_SUPPORT`), `conditions` |
| `item` | Uses an inventory item | `name` (from `ITEMS`), `conditions` |
| `toggle` | Toggles Spirit Stance/Defend/Focus | `name`, `toggled` (boolean), `conditions` |
| `target` | Selects a target monster for subsequent actions | `priorityRule` (from `PRIORITY_RULES`), `conditions` |
| `smartDebuff` | Casts AoE debuffs with spatial targeting | `name`, `targetCount` (1-3), `bottomUp`, `tailSkip`, `maxAtFirst`, `minMonstersLeft`, `conditions` |
| `spellDebuff` | Casts single-target debuff on selected monster | `name`, `conditions` |
| `spellDamage` | Casts offensive spell on selected monster | `name` (can be `T1`/`T2`/`T3` aliases), `conditions` |
| `skill` | Uses a weapon skill | `name` (from `SKILLS`), `conditions` |
| `normalAttack` | Basic attack on selected monster | None |
### 2.3 Condition System
#### Condition Categories
**General Conditions** (`conditionsGeneral`, lines 947-979): Checked against global/player state:
- `world` — Persistent vs Isekai
- `pLevel` — Player level range
- `pMaxSpellType` — Player's strongest spell element
- `battleTypes` — Battle type(s) (Arena, Encounter, etc.)
- `difficulty` — Difficulty array
- `roundCurrent`, `roundLeft`, `roundTotal` — Round position
- `floor` — Tower floor range
- `pActionCooldown` — Cooldown range of specific actions (skill/spell/item)
- `pActionCounts` — Uses-per-round range of specific actions
- `pHP`, `pMP`, `pSP` — Player resource percentages
- `pOC` — Overcharge percentage
- `pSpiritStatus` — Spirit Stance active?
- `pEffects` — Player has effects with turns in range
- `pIgnoredEffects` — Player does NOT have these effects
- `pEffectStacks` — Player effect stack counts
- `monsters`, `activeMonsters`, `defeatedMonsters` — Monster counts
- `bosses`, `activeBosses`, `defeatedBosses` — Boss counts
- `mLevel` — Monster level range
- `mWithoutEffects` — Count of monsters without specific effects
**Target Conditions** (`conditionsTarget`, lines 980-999): Checked per-monster with offset/matched mechanics:
- `tName` — Regex-matchable monster name
- `tTypes` — Monster type (Normal, Rare, Legendary, etc.)
- `tClasses` — Monster class from MonsterDB (Arthropod, Avion, etc.)
- `tPowerLevel` — Monster power level range
- `tIndex` — Monster position (0-based, negative from end)
- `tHP`, `tMP`, `tSP` — Monster resource percentages
- `tEffects` — Monster has specific effects
- `tIgnoredEffects` — Monster does NOT have specific effects
- `tEffectStacks` — Monster effect stacks
- `tDaysSinceUpdate` — Days since MonsterDB last updated entry
Each target condition carries `offset` (range relative to target's index) and `matched` (count of monsters in the offset range that must satisfy the condition — defaults to `[1,1]` = exactly 1).
#### Condition Evaluation (`checkConditions`, line 2579)
```javascript
function checkConditions(conditions, target, checkGlobal, checkTarget)
```
- Iterates through all conditions, short-circuiting on first failure (AND logic)
- Global conditions: checked directly against global state objects
- Target conditions: evaluated with offset-based multi-monster checking
- `successCount` increments for each active monster in `[target.index + offset[0], target.index + offset[1]]` that satisfies the condition
- Final check: `jpxUtils.inRange(successCount, matched)`
The conditions system is lazily initialized via `initConditions()` (line 2445) which creates handler maps. Results are cached in `conditionsObj` per evaluation cycle (cleared in `preRender()`).
### 2.4 Action Execution (`actionManager`, line 2734)
The action manager processes action sequences with a sophisticated targeting phase:
```
actionManager(actions):
targetPhase = false
targetMonster = null
targetLocked = false
for each action in actions:
if action.disabled → skip
if action.type == 'target':
targetPhase = true
if not targetLocked:
result = handler(action, null) // evaluates target conditions globally, returns matching monster or null
if result found:
targetMonster = result
targetLocked = true // Lock in the first matching target
else if targetPhase and no targetMonster → skip (no target to act on)
else if handler(action, targetMonster) succeeds → return true (action taken)
targetLocked = false // Unlock for next target rule
return false // No action was possible
```
Key behaviors:
- **Target rules cascade**: Once a target rule finds a monster, `targetLocked` prevents re-evaluation until an action is successfully executed
- **Smart Debuff skips target phase**: `smartDebuff` actions are checked globally (conditions must pass without target), then internally handle their own targeting
- **Non-target actions after target rules require a locked target**, but "stop"/"item"/"spellSupport"/"toggle" can execute without a target if they appear before any target rule
### 2.5 Smart Debuff System (`doSpellsDebuffGoNext`, line 1810)
This is the most advanced targeting system in the script. It handles multi-target AoE debuff placement:
**Parameters:**
- `name` — Spell name
- `targetCount` — 1, 2, or 3 monsters to hit
- `bottomUp` — Iterate from monster J→A (true) or A→J (false)
- `tailSkip` — Skip last N monsters (positive) or only consider first N (negative)
- `maxAtFirst` — Maximum casts when ≤1 monster defeated
- `minMonstersLeft` — Threshold for infinite casting when many monsters remain
**Algorithm:**
1. Check cooldown and action count limits
2. Calculate `startIndex`/`endIndex` range from `activeMonsters` bounds
3. `tailSkip`: if positive, trim from end; if negative, limit to first N from start
4. First pass (targetCount ≥ 3): Find 3 consecutive alive + undebuffed monsters → cast on middle
5. Second pass (targetCount ≥ 2): Find 2 consecutive alive + undebuffed → cast on edge-appropriate monster
6. Third pass (targetCount ≥ 3, relaxed): Find 3 consecutive where middle is alive → cast on middle
7. Fourth pass (targetCount = 1): Any single undebuffed monster → cast
**`isUnDebuffed` memoization** (line 1843): Results cached in `undebuffedObj` to avoid re-evaluating conditions for the same index.
### 2.6 Priority Rules for Target Selection
Six rules (line 658):
- **Top Down** — Default, monster A first
- **Bottom Up** — Monster J first
- **Current HP Low to High** — Sort by absolute HP
- **Current HP High to Low** — Sort by absolute HP descending
- **Current HP Percent Low to High** — Sort by HP percentage
- **Current HP Percent High to Low** — Sort by HP percentage descending
Results are cached in `monstersObj.sorted` per priority rule per cycle.
---
## 3. DOM PARSING
### 3.1 Monster State (`getMonsters`, line 2191)
Parses all `.btm1` elements to build monster objects:
```javascript
monster = {
index, // 0-9
click(), // clicks the monster DOM element
level, // from .btm2 div text or HVClasses
name, // from .btm3 div text or HVClasses
type, // matched against bossTypes map (Normal/Rare/Legendary/etc.)
hpPercentage, // from .btm4 green bar width / 120
mpPercentage, // from .btm4 blue bar width / 120
spPercentage, // from .btm4 red bar width / 120
isAlive, // .btm1 has onclick attribute
effectObj: { name: { turns, stack } }, // from .btm6 img tooltips
monster_btm1 // reference to DOM element
}
```
Returns: `{ monsters, activeMonsters, bosses, activeBosses }`
### 3.2 Player Vitals (`getVitals`, line 2091)
Two code paths:
- **With OC bar** (Arena/Tower): `widthHP=414, widthMP/SP/OC=414`, reads `#dvrhb/#dvrhd`, `#dvrm`, `#dvrs`
- **Without OC bar** (Encounter/Grindfest): `widthHP=496, widthMP/SP=207`, reads `#vrhb/#vrhd`, `#vrm`, `#vrs`
OC calculation:
- With bar: `10 * parseInt(ocBar.style.width) / 414`
- Without bar: Count `<div>` elements in `#vcp` HTML (each `div` = 0.5 OC after the first), handle half-segment via `vcr` class
Returns: `{ oc, hpPercentage, mpPercentage, spPercentage, hpCurrent, mpCurrent, spCurrent, hpMax, mpMax, spMax }`
### 3.3 Action Cooldowns (`getActionCooldowns`, line 1950)
Dual scanner:
- **Spell cooldowns** (`.bts > div[onmouseover]`): Parse `onmouseover` tooltip with `regExp.spellInfo` → extract name and cooldown. If element has `onclick`, cooldown = 0 (ready). Otherwise: `lastUse + initCooldown - currentTurn`
- **Item cooldowns** (`.bti3 > div`): Parse with `regExp.itemInfo` → map item ID to name via `itemMap`. Fixed 40-turn cooldown.
Cooldown is `'-'` if still on cooldown, `0` if ready.
### 3.4 Player Effects (`getEffectDuration`, line 2145)
Parses `#pane_effects` images and `.btm6 > img` elements:
- Extract from `onmouseover` tooltip using `regExp.spellMatch` with named groups: `name`, `stack`, `description`, `turns`
- Builds `playerEffectsObj[name] = { turns, stack }`
- When `render=true`: creates duration overlay divs with color coding (<4 turns aquamarine, <9 lavender, auto/permanent special display)
### 3.5 Monster Effects Tracking (`updateMonsterEffects`, line 2291)
Handles the game's limitation of only showing 5 status effects per monster:
- **5 effects visible**: Reset saved state completely
- **6 effects visible**: The 6th is hidden behind a scroll the script:
1. Calculates hidden turn deltas by comparing saved vs visible effect turns
2. Parses battle log for effect gains/expirations (`regExp.effectGain`, `regExp.effectExpired`, `regExp.effectWear`, etc.)
3. Applies turn decrement to hidden effects
4. Special handling for elemental effects (Searing Skin, Freezing Limbs, etc.) and Coalesced Mana
5. Prunes effects that would have expired
6. Renders hidden effect icons into the `.btm6` DOM
### 3.6 Monster Info (MonsterDB Integration, line 2404)
Polls `window.HVMonsterDB.getCurrentMonstersInformation()` with 250ms timeout for:
- `monsterClass` e.g., "Arthropod", "Daimon"
- `attack` attack type
- `plvl` power level
- `lastUpdate` timestamp for `tDaysSinceUpdate` condition
Stored in `allMonsterInfo` keyed by `mkey_{0-9}`.
### 3.7 Spell/Item Casting (`cast` / `use`, lines 1928-1948)
**cast(name):** Searches `.bts > div[onclick][onmouseover*="'name'"]` for spell buttons. If current selected spell differs, fires `onmouseover` (to select) then `onclick` (to cast) via a dummy div.
**use(name):** Looks up item ID from `itemMap`, finds matching div in `.bti3 > div[onclick][onmouseover]` containing that ID, fires `onmouseover` + `onclick`.
### 3.8 Spirit Status (`getSpiritStatus`, line 2139)
Checks if `#ckey_spirit` outerHTML contains `spirit_a.png` (active) or not (inactive).
---
## 4. FIGHTING STYLES
### 4.1 Auto-Detection (`initDoBattle`, line 1364)
Detection is based on which skills appear in the spell quickbar:
| Available Skill | Style (low spell damage) | Style (high spell damage) | Threshold |
|----------------|--------------------------|---------------------------|-----------|
| Shield Bash | `OneHanded` | `1H_Mage` | 70 |
| Great Cleave | `TwoHanded` | `2H_Mage` | 100 |
| Iris Strike | `DualWielding` | `DW_Mage` | 100 |
| Skyward Sword | `NitenIchiryu` | `NI_Mage` | 100 |
| Concussive Strike | `Staff` | `Staff` | N/A |
| None of above | `Unarmed` | `Unarmed` | N/A |
The threshold compares `spellDamageBonus.maxValue` (from `localStorage['jpx_spellDamageBonus']`) the player's maximum spell damage bonus value. Above threshold = mage variant.
### 4.2 Battle Mode Resolution (`getBattleMode`, line 4173)
```javascript
getBattleMode(defaultBattleStyle = 'Unarmed'):
modeKey = `${battleStyle}_${battleType}` // e.g., "OneHanded_Arena"
if cfgBattle[modeKey] has non-empty supports, attacks, or key-bindings:
return modeKey // Specific mode exists
return `${battleStyle}_General` // Fall back to general mode
```
This means: check for a battle-type-specific config first, fall back to general.
### 4.3 Battle Type Detection (`initDoBattle`, lines 1410-1428)
Parses `#textlog` innerHTML with `regExp.battleTypeLog`:
- `arena challenge` + NOT `Round 1 / 1` `Arena`
- `random encounter` `Encounter`
- `arena challenge` + `Round 1 / 1` `Colosseum`
- `Grindfest` `Battle1000`
- `Item World` `Item`
- `The Tower` `Tower` (also extracts floor number)
### 4.4 Style-Specific Configurations (Default)
The bundled defaults show the script author's own configurations:
- **OneHanded_General**: Full support chain (SpiritHealthMana gems/potions/elixirs, Mystic Gem, Heartseeker, Regen, Draughts), attacks use smartDebuff (Weaken for Arena400/500), target rules by priority, Scan first round, skills (OFC, Merciful Blow, Vital Strike), ending with normalAttack
- **OneHanded_Tower**: Complex multi-stage smart debuff chain (SleepWeakenSilenceImperil) triggered by floor/round thresholds
- **Staff_General**: Mage-focused supports (Arcane Focus instead of Heartseeker), smartDebuff chain (WeakenSilenceImperil), target priority for Coalesced Mana monsters, T3T2T1 spell priority
- Other styles (2H, DW, Niten, Unarmed, mage variants): Empty default configs (lines 126-173, 221-228) users configure them
---
## 5. BATTLE MODES
### 5.1 Enumeration (LINE 656-657)
```javascript
BATTLE_TYPES = ['Arena', 'Encounter', 'Colosseum', 'Battle1000', 'Item', 'Tower']
BATTLE_STYLES = ['OneHanded', '1H_Mage', 'TwoHanded', '2H_Mage', 'DualWielding', 'DW_Mage', 'NitenIchiryu', 'NI_Mage', 'Staff', 'Unarmed']
BATTLE_MODES = Cartesian product BATTLE_STYLES × ['General', ...BATTLE_TYPES]
```
### 5.2 Mode-Specific Behavior
| Battle Type | Special Behavior |
|-------------|-----------------|
| **Arena** | Sets difficulty to PFUDOR if 90 rounds. Uses roundInfo from localStorage for persistence |
| **Encounter** | Round info not tracked. Stamina cost = 0 |
| **Colosseum** | Single round. Stamina cost = 0 |
| **Battle1000** (Grindfest) | Stamina cost includes +1 entry fee |
| **Item** (Item World) | Reads worldLevel from localStorage (populated in lobby from IW equipment screen). Stamina cost formula with great/normal rates |
| **Tower** | Auto-derives difficulty from floor number (1-6 Normal, 7-13 Hard, ..., 34-39 IWBTH, 40+ PFUDOR). Caches floor in localStorage |
### 5.3 Difficulty Map (line 574)
```javascript
{ Normal: 1, Hard: 2, Nightmare: 4, Hell: 7, Nintendo: 10, IWBTH: 15, PFUDOR: 20 }
```
### 5.4 Stamina Cost Calculation (`getStaminaCost`, line 3172)
- Great cost: 0.03 (Persistent) or 0.06 (Isekai) per round
- Normal cost: 0.02 (Persistent) or 0.04 (Isekai) per round
- Great rounds capped by `floor(max(0, stamina - 60) / greatCost)` (60 stamina reserved for normal-cost rounds)
- Battle1000 adds +1 entry fee
- Daily quota tracking via `staminaRecords` in localStorage per date
---
## 6. KEYBINDINGS & UI
### 6.1 Hotkey System
**Global Keybinds** (line 912):
```javascript
KEYBINDS = {
openBattleRecords: { key: 'z', ctrl: false },
toggleActive: { key: 'm', ctrl: false },
openSettings: { key: ',', ctrl: false }
}
```
User-customizable, stored in `localStorage['jpx_userKeybinds' + isekaiSuffix]`.
**Battle Action Keybindings**: Stored as `kb_Ctrl+Shift+A` style keys in each battle mode config. Processed in `onKeyDown()`:
1. Check for global keybind match (non-repeat only)
2. If inactive and `#textlog` exists and monsters are alive, check for mode-specific keybinding
3. Repeat keys use throttled `actionManager` (75ms), initial press uses direct `actionManager`
**Key Capture** (`jpxUtils.captureKeyCombo`, line 6060): Uses `AbortController` for clean cancellation. Ignores modifier-only keypresses. Fires on first non-modifier keydown or pointerdown.
### 6.2 Control Widget (`ctrlWidget`)
Created by `jpxPanelManager.createCtrlWidget('battle')`:
- Positioned absolutely at top-right of battle screen (responsive via media query for landscape)
- Shows configurable rows (`ctrlWidgetRows`): Active status, Ready state, Network delay, Battle style, Battle type, Battle mode, Round
- Background color signals state: green (`#4f4`) = active, pink (`#fef`) = inactive, yellow (`#ff5`) = warning
- Click toggles auto-battle
- Sub-buttons: "Open Stats" opens battle records in new window; "Open Settings" opens settings panel
- Optional `mouseEnter` trigger for auto-battle activation
- Dispatches `jpx_ctrlWidget_update` CustomEvent for inter-script communication
### 6.3 Settings Panel (press `,`)
`renderSettings()` creates a full in-page settings panel with:
- **Two tabs**: Battle Settings, Stats Settings
- **Battle tab**: Keybind remapping UI, full schema-driven config editor for all battle modes, export/import/reset current mode
- **Stats tab**: Dark mode toggle, combat/revenue row picker, stats column picker, IndexedDB export/import
- **Schema-driven rendering**: `renderSchema()` with `fieldRenderers` registry supporting: heading, constant, boolean, text, number, rangeNumber, dropdown, array (with drag-and-drop reordering), fieldPicker (dual-list), object, keyBasedObjectArray (for keybindings), conditionsArray
### 6.4 Toast Notifications
`jpxUtils.createToast(content, duration)` fixed-position toast at bottom-right with fade animation.
---
## 7. STORAGE
### 7.1 localStorage (prefix: `jpx_`, suffixed with `_isekai` for Isekai)
| Key | Data | Purpose |
|-----|------|---------|
| `cfgBattle` | Full battle config | User rules, passed through mergeCfg |
| `cfgStats` | Stats display config | Combat/revenue display preferences |
| `userKeybinds` | Custom keybindings | Overrides for global hotkeys |
| `spellDamageBonus` | `{maxType, maxValue}` | Spell element optimization |
| `worldLevel` | Number | IW world level |
| `difficulty` | String | Current difficulty |
| `playerLevel` | Number | Player level |
| `persona` | String | Active persona |
| `stamina` | Number | Current stamina |
| `battleType` | String | Last battle type |
| `towerFloor` | Number | Current tower floor |
| `roundInfo` | `{current, total}` | Round tracking across page loads |
| `monsterData` | Array of `{id, name, level, maxHP}` | Monster data for HP calculation |
| `battleLogRecord` | Array of strings | Raw battle log (if enabled) |
| `timeRecords` | `{action, turn, riddle, lastUse}` | Per-battle timing |
| `combatRecords` | Nested damage/result stats | Per-battle combat data |
| `revenueRecords` | Nested drop/currency stats | Per-battle revenue tracking |
| `staminaRecords` | `{lastUpdate, staminaCost}` | Daily stamina quota tracking |
| `priceData` | Market prices | Cached once per day |
### 7.2 IndexedDB
**Database**: `jpx`, version 1
**Object Store**: `battleRecords` with keyPath `timestamp`, index on `date`
Used for persistent battle history. Records contain: world, timestamp, date, playerLevel, difficulty, persona, battleType, worldLevel, towerFloor, roundInfo, result, deltaSeconds, deltaTime, turns, tps, riddle, combatRecords, revenueRecords.
**Operations**:
- `openDB()` Open/create database
- `storeBattleRecords()` Save after battle completion
- `getBattleRecordsRender()` Query with filters (aggregate by day, world, battleType, difficulty, result, round range)
- `exportIndexedDB()` / `importIndexedDB()` Full DB export/import with optional merge
### 7.3 State Management
- `storeTmp()` (beforeunload): Persists monsterData, battleLogRecord, timeRecords, combatRecords, revenueRecords but only if battle is not complete (no finish button)
- On battle completion: `localStorage.removeItem()` clears all temp records
- Version-aware config migration via `mergeCfg()` comparing `battleVersion`/`statsVersion`
---
## 8. COMPATIBILITY
### 8.1 Monsterbation (Monster DB Script) Integration
The script integrates with `window.HVMonsterDB` (Monsterbation's monster database):
- `monsterDBReady()` (line 2404): Polls `window.HVMonsterDB.getCurrentMonstersInformation()` with 250ms timeout
- Used for: `monsterClass`, `attack` type, `plvl` (power level), `lastUpdate` date
- `tClasses`, `tPowerLevel`, `tDaysSinceUpdate` conditions depend on this data
- `showMonsterInfo` option displays monster class, attack type, and power level on each monster
- Falls back gracefully (`'?'` display) when MonsterDB is unavailable
### 8.2 Inter-Script Communication
`jpx_ctrlWidget_update` CustomEvent is dispatched by `jpxPanelManager.dispatchState()` (line 5614) with:
```javascript
{ active: isActiveBattle, background: color, suffix: isekaiSuffix, timestamp }
```
This is the designated hook for other userscripts to react to jpx state changes.
### 8.3 AJAX Round Compatibility
The `ajaxRound` feature has a config flag with explicit warning: "Disable it if other scripts don't support it." When enabled, it replaces standard page navigation with XHR fetches, which may break other scripts that rely on full page loads.
### 8.4 DOM Content Loaded Simulation
On AJAX round advancement, the script fires `document.dispatchEvent(new Event('DOMContentLoaded'))` (line 1680) to retrigger other scripts' initialization. It also re-creates `window.battle = new window.Battle()` and calls `clearInterval(window.timer)`.
---
## 9. KEY FUNCTIONS
### Initialization & Lifecycle
| Function | Line | Purpose |
|----------|------|---------|
| `initDo()` | 1242 | Router: detects page context and dispatches to battle/lobby/riddle flows |
| `initDoBattle()` | 1364 | Battle initialization: load configs, detect style/type, parse initial state |
| `reDoBattle()` | 1457 | Re-initialization on round transition / DOMContentLoaded |
| `preDoBattle()` | 1476 | Parse round info, proficiency, monster data; setup MutationObserver |
| `preProcessLog()` | 1547 | MutationObserver callback: processes new log entries |
| `preRender()` | 1580 | Prepares all state objects (cooldowns, vitals, monsters, effects) for decision |
| `goNext()` | 1614 | Main loop: checks if active, handles end-of-round, triggers smartBattle |
| `storeTmp()` | 5469 | beforeunload handler: persists temp state |
### Battle Logic
| Function | Line | Purpose |
|----------|------|---------|
| `smartBattle()` | 1792 | Evaluates supports then attacks; returns whether action was taken |
| `actionManager()` | 2734 | Processes action sequences with target locking |
| `checkConditions()` | 2579 | Universal condition evaluator |
| `initConditions()` | 2445 | Builds general/target handler maps |
| `doSpellsDebuffGoNext()` | 1810 | Smart debuff spatial targeting algorithm |
| `doSpellGoNext()` | 1897 | Cast spell on specific monster |
| `doAttackGoNext()` | 1907 | Normal attack on specific monster |
| `doToggleGoNext()` | 1916 | Toggle Spirit/Defend/Focus |
| `cast()` | 1928 | Cast named spell via DOM |
| `use()` | 1937 | Use named item via DOM |
### DOM Parsing
| Function | Line | Purpose |
|----------|------|---------|
| `getActionCooldowns()` | 1950 | Parse spell/item cooldowns from quickbar DOM; render quickbar extensions |
| `getVitals()` | 2091 | Parse HP/MP/SP/OC from bar elements |
| `getSpiritStatus()` | 2139 | Check if Spirit Stance is active |
| `getEffectDuration()` | 2145 | Parse player/monster effect durations from tooltips; render overlays |
| `getMonsters()` | 2191 | Build monster state objects from `.btm1` elements |
| `updateMonsterEffects()` | 2291 | Track hidden (6th+) monster status effects |
| `updateMonsterInfo()` | 2428 | Poll MonsterDB for monster class/power/attack data |
| `monsterDBReady()` | 2404 | Async poller for MonsterDB availability |
### Battle Recording
| Function | Line | Purpose |
|----------|------|---------|
| `battleRecorder()` | 2767 | Orchestrates all recording subsystems |
| `battleLogRecorder()` | 2789 | Records raw battle log text |
| `timeRecorder()` | 2823 | Tracks actions, turns, last use timestamps |
| `riddleRecorder()` | 2830 | Counts RiddleMaster solves |
| `combatRecorder()` | 2839 | Comprehensive damage/result tracking with type classification |
| `revenueRecorder()` | 3012 | Tracks EXP, credits, drops by category and quality |
### Battle Results Display
| Function | Line | Purpose |
|----------|------|---------|
| `battleRecordPlayer()` | 3127 | Orchestrates end-of-battle record display and DB storage |
| `battleLogPlayer()` | 3290 | Creates downloadable battle log blob |
| `timeRecordPlayer()` | 3301 | Creates time/riddle/spark summary div |
| `combatRecordPlayer()` | 3313 | Creates combat stats table (damage, results, crit stacks, debuff resists) |
| `combatRecordPlayer_Use()` | 3438 | Creates action usage summary table |
| `revenueRecordPlayer()` | 3478 | Creates revenue table with drop/use/balance/profit |
| `newWindowRecordPlayer()` | 3605 | Renders mid-battle stats in popup window |
### IndexedDB & Stats
| Function | Line | Purpose |
|----------|------|---------|
| `openDB()` | 3672 | Open/create IndexedDB |
| `storeBattleRecords()` | 3695 | Save battle record |
| `openBattleRecords()` | 3711 | Open stats window with filters |
| `getBattleRecordsRender()` | 3799 | Query/filter/aggregate battle records |
| `exportIndexedDB()` | 3851 | Full DB export |
| `importIndexedDB()` | 3873 | DB import with merge support |
| `filterData()` | 3908 | Apply filters to records |
| `generateAggregate()` | 3918 | Aggregate data by day (Total/Average) |
| `createFilter()` | 4028 | Build filter UI |
| `getFilters()` | 4080 | Read filter values from UI |
| `renderDynamicTable()` | 4107 | Render stats table with color thresholds |
### Settings & Schema
| Function | Line | Purpose |
|----------|------|---------|
| `renderSettings()` | 5094 | Open/close settings panel |
| `switchTab()` | 5265 | Switch between battle/stats tabs |
| `renderBattleTab()` | 5288 | Battle settings tab with keybinds + full config editor |
| `renderStatsTab()` | 5381 | Stats settings tab with DB export/import |
| `renderSchema()` | 4189 | Schema-driven form renderer |
| `renderField()` | 4989 | Field dispatcher to type-specific renderers |
| `resolveSchema()` | 4994 | Resolve discriminator-based schemas |
| `createEmptyObject()` | 5000 | Generate default object from schema |
| `getUniqueId()` | 5030 | Generate unique DOM IDs |
| `mergeCfg()` | 5034 | Merge stored config with defaults, run patches/migrations |
### Configuration
| Function | Line | Purpose |
|----------|------|---------|
| `getBattleMode()` | 4173 | Resolve active battle mode key (e.g., "OneHanded_Arena" "OneHanded_General") |
### Keybinding
| Function | Line | Purpose |
|----------|------|---------|
| `onKeyDown()` | 1746 | Global keydown handler |
| `toggleActive()` | 1782 | Toggle auto-battle on/off |
### I18N
| Function | Line | Purpose |
|----------|------|---------|
| `initDoI18n()` | 5491 | Merge built-in I18N with external `jpxI18N` override |
| `t()` | 5505 | Translation function with dot-path lookup and templating |
### Modules (singleton pattern)
| Module | Line | Purpose |
|--------|------|---------|
| `jpxPanelManager()` | 5526 | Control widget creation, content update, state dispatch |
| `jpxMarket()` | 5632 | Market price fetching, caching, default prices |
| `jpxUtils()` | 5797 | 25 utility functions: throttle, time formatting, sorting, type checks, DOM helpers, XHR, key capture, HV class parsing, record factories |
### Utility Functions (jpxUtils)
| Function | Purpose |
|----------|---------|
| `throttle(fn, ms, trailing)` | Rate-limit function calls |
| `secondsToTime(s, ms)` | Convert seconds to HH:MM:SS |
| `daysSince(dateStr)` | Days since UTC date |
| `getSortedArray(arr, fn, asc)` | Sort array by computed value |
| `lowerFirst(str)` | Lowercase first character |
| `titleCase(str)` | Title-case with camelCase splitting |
| `sentenceCase(str)` | Sentence-case |
| `matchAny(str, ...regexps)` | Test multiple regexes, return first match |
| `parseValue(val)` | Parse string to number if numeric |
| `isEmpty(obj)` | Check if object has no own properties |
| `inRange(value, [min, max])` | Range check |
| `getValueByPath(obj, keys)` | Dot-path object access |
| `deepMerge(target, source)` | Recursive object merge |
| `getSortedKeys(order, keys)` | Sort keys by predefined order |
| `inc(obj, key, step)` | Increment object property |
| `createButton(container, opts)` | Create styled button with toast feedback |
| `createToast(content, dur)` | Show toast notification |
| `stringifyLimited(obj, level)` | Pretty-print JSON with depth limit |
| `toRegExp(input)` | Parse regex string or create literal regex |
| `captureKeyCombo(onComplete, onAbort)` | Listen for single key combo |
| `formatKeyCombo(input, sep)` | Format key combo object/string for display |
| `parseHVClasses(container)` | Decode HentaiVerse obfuscated class names |
| `createTimeRecords()` | Factory for empty time records |
| `createCombatRecords()` | Factory for empty combat records |
| `createRevenueRecords()` | Factory for empty revenue records |
| `xhrGet(urlArray, interval)` | Staggered XHR requests with Promise.allSettled |
---
## Summary Statistics
- **Total lines**: 6,215
- **Named functions**: ~45
- **Utility functions**: 25
- **Regular expressions**: ~55 defined in `regExp` object
- **Supported fighting styles**: 10 (5 physical + 5 mage variants)
- **Supported battle types**: 6 + "General" fallback
- **Total battle mode combinations**: 60
- **Action types**: 10
- **General condition keys**: 25
- **Target condition keys**: 12
- **Priority rules**: 6
- **IndexedDB object store**: 1 (battleRecords)
- **localStorage keys**: ~20+
- **Singleton modules**: 3 (PanelManager, Market, Utils)
- **Default market prices**: ~80 items
- **Monster boss types**: 30+ across 6 tiers
- **I18N entries**: ~150+