Skip to content

Fix bot party formation backlog - #80

Merged
pmbstyle merged 2 commits into
mainfrom
agent/party-formation-performance
Aug 1, 2026
Merged

Fix bot party formation backlog#80
pmbstyle merged 2 commits into
mainfrom
agent/party-formation-performance

Conversation

@pmbstyle

@pmbstyle pmbstyle commented Aug 1, 2026

Copy link
Copy Markdown
Owner

This PR fixes the bot party backlog and the progression flow around it.

Bots now classify party needs from their actual gear and target level, with explicit required/preferred reasons. When a compatible group is not available, a bot can continue from a safe solo fallback instead of remaining idle in the party queue. Party requests now carry a concrete route/NPC objective, expire and cool down when stale, are cleaned up for passive or dead bots, and are cleared correctly when a bot joins or leaves a party.

Formation now groups compatible objectives, gives required requests priority without letting an incompatible request block other groups, reclaims unused capacity when needed, and rotates party sessions with jitter so groups do not dissolve in one synchronized wave.

The scheduler and formation work is bounded and cooperative to reduce event-loop spikes. Population telemetry now reports request reasons, formation stage timings, p95 duration, budget stops, queue age, scheduler lag, and overruns. Also added a small null-safety fix for craft travel telemetry.

Validation:

  • npm test
  • npm run check
  • git diff --check
  • Live restart and x50 population telemetry verification

Summary by CodeRabbit

  • New Features

    • Improved background party formation by grouping bots with compatible objectives and routes.
    • Added safer solo fallback behavior when required parties are unavailable.
    • Added party request priorities, expiration, cooldowns, and session rotation handling.
    • Improved support for craft-related party objectives and NPC targets.
  • Bug Fixes

    • Recovered stalled or orphaned party requests automatically.
    • Prevented cleared travel data from causing errors.
    • Improved gear readiness checks for weapons, armor, and party requirements.
  • Monitoring

    • Added party formation timing, scheduler budget, request, and lifecycle details to status reporting.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR updates gear acquisition with structured party requirements and safe fallbacks. It adds party-request cleanup, objective-based formation, session expiry, deadline-aware scheduling, database indexes, and expanded population telemetry.

Changes

Party acquisition and population lifecycle

Layer / File(s) Summary
Structured gear party requirements
src/GameServer/Bot/AI/GearAcquisitionPlanner.js, src/GameServer/Bot/Economy/CraftTelemetry.js, tests/test_bot_gear_acquisition.js, tests/test_bot_craft_telemetry.js
Gear sources report solo_ok, preferred, or required party needs with reasons. Required plans select safe fallback sources. Craft telemetry accepts null travel state.
Party request lifecycle and state recovery
src/GameServer/Bot/Population/BackgroundPartyState.js, src/GameServer/Bot/Population/BotLifeState.js, src/GameServer/Bot/Population/PersonaPartyPolicy.js, src/GameServer/Bot/Population/PopulationConfig.js, tests/test_bot_population_state.js, tests/test_bot_party_wait.js
Startup recovery, request expiration, candidate selection, assignment, departure, death handling, policy acceptance, and session rotation manage party-request metadata.
Objective-based party formation
src/GameServer/Bot/Population/PopulationService.js, tests/test_bot_background_party_recruitment.js
Formation groups compatible acquisition objectives, resolves objective spots and NPC targets, refreshes requirements, recruits members, and stores objective metadata.
Deadline scheduling and population telemetry
src/GameServer/Bot/Population/PopulationService.js, src/GameServer/Bot/Population/PopulationMetrics.js, src/GameServer/Bot/Population/PopulationStatus.js, database/sql/sqlite.sql
Population processing uses deadlines, lag checks, cooperative slices, budget-stop metrics, formation timings, party-request counts, status output, and composite party-request indexes.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant BotLifeState
  participant PopulationService
  participant GearAcquisitionPlanner
  participant BackgroundPartyState
  BotLifeState->>PopulationService: provide open party requests
  PopulationService->>GearAcquisitionPlanner: evaluate party need and fallback
  PopulationService->>BackgroundPartyState: group and persist objective party
  BackgroundPartyState-->>BotLifeState: return normalized party state
Loading

Possibly related PRs

  • pmbstyle/L2Solo#77: Related BotLifeState, PopulationService, and background-party lifecycle changes.
  • pmbstyle/L2Solo#78: Related party requirements and companion behavior changes.
  • pmbstyle/L2Solo#79: Related party recruitment, lifecycle management, and scheduling changes.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the primary change: fixing the bot party formation backlog.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent/party-formation-performance

Comment @coderabbitai help to get the list of available commands.

@pmbstyle pmbstyle self-assigned this Aug 1, 2026
@pmbstyle
pmbstyle marked this pull request as ready for review August 1, 2026 21:01
@pmbstyle
pmbstyle marked this pull request as draft August 1, 2026 21:04

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (5)
src/GameServer/Bot/Population/PopulationService.js (1)

206-208: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the unused partyRequestActive helper.

ESLint reports partyRequestActive as defined but never used. No call site appears in the changed code, and it is not exported at lines 347-360.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/GameServer/Bot/Population/PopulationService.js` around lines 206 - 208,
Remove the unused partyRequestActive function from PopulationService.js, leaving
the surrounding party request logic and exports unchanged.

Source: Linters/SAST tools

src/GameServer/Bot/AI/GearAcquisitionPlanner.js (2)

459-475: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the redundant hasWeapon term in unpreparedSupport.

Line 470 returns required for every state without a weapon. The !readiness.hasWeapon term inside unpreparedSupport is therefore never decisive. Keep the armor condition only, so the intent of the support check stays clear.

♻️ Proposed simplification
-    const unpreparedSupport = ['healer', 'buffer'].includes(readiness.role)
-        && (!readiness.hasWeapon || readiness.armorCount < 2);
+    const unpreparedSupport = ['healer', 'buffer'].includes(readiness.role)
+        && readiness.armorCount < 2;
     if (!readiness.hasWeapon) return { need: 'required', reason: 'missing_weapon' };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/GameServer/Bot/AI/GearAcquisitionPlanner.js` around lines 459 - 475,
Simplify the unpreparedSupport condition in partyNeedAssessmentForSource by
removing the redundant readiness.hasWeapon check and retaining only the
support-role and armor-count conditions; leave the separate missing_weapon
return unchanged.

627-630: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Compute the party-need assessment once per source.

Lines 627-630 evaluate the same source three times: soloSafeForSource, partyNeedForSource, and partyNeedReasonForSource. Each call re-runs combatReadiness, which scans and reduces the equipped inventory. Lines 679-684 repeat the same pattern for next. Export the assessment helper, or compute it once and derive the three fields.

♻️ Proposed refactor for lines 679-685
-        const partyNeed = !readyToCraft && !componentReady && next
-            ? partyNeedForSource(state, next)
-            : 'solo_ok';
-        const partyNeedReason = !readyToCraft && !componentReady && next
-            ? partyNeedReasonForSource(state, next)
-            : 'solo_ready';
-        const requiresParty = partyNeed === 'required';
+    const nextAssessment = !readyToCraft && !componentReady && next
+        ? partyNeedAssessmentForSource(state, next)
+        : { need: 'solo_ok', reason: 'solo_ready' };
+    const partyNeed = nextAssessment.need;
+    const partyNeedReason = nextAssessment.reason;
+    const requiresParty = partyNeed === 'required';

Also applies to: 679-685

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/GameServer/Bot/AI/GearAcquisitionPlanner.js` around lines 627 - 630,
Update the source assessment logic in the planner block around the status object
and its corresponding next-source block to compute the party-need assessment
once per source, then derive soloSafe, partyNeed, partyNeedReason, and
requiresParty from that shared result. Reuse the same assessment for both the
current source and next source paths, or export and reuse the underlying
assessment helper, avoiding repeated combatReadiness scans while preserving
existing field values.
tests/test_bot_background_party_recruitment.js (1)

86-98: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for candidate claiming across two recruiting parties.

The new assertions cover objective separation and craft NPC targeting. They do not cover the claimed set in recruitBackgroundMembers. Add a case with two active parties that both lack stats.objective and share a spot. Then assert that each candidate is assigned exactly once. That case fails today because of the precedence defect flagged in src/GameServer/Bot/Population/PopulationService.js at lines 1229-1237.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_bot_background_party_recruitment.js` around lines 86 - 98, Extend
the recruitment tests around groupPartyCandidatesByObjective with two active
parties sharing a spot and neither having stats.objective, then verify each
candidate is assigned exactly once across both parties. Exercise
recruitBackgroundMembers and its claimed-set handling so the regression exposes
the precedence defect without changing the existing objective-separation or
craft NPC assertions.
src/GameServer/Bot/Population/BotLifeState.js (1)

1253-1279: 🚀 Performance & Scalability | 🔵 Trivial

Verify index coverage for the widened json_extract-based candidate queries.

These queries now filter and group on json_extract(statsJson, '$.partyRequest.status'), '$.partyRequest.priority'), and a COALESCE of three JSON/column expressions (objectiveSpot), across three activities (hunting, resting, party_wait) instead of one. SQLite cannot use a normal index on json_extract() expressions unless a generated column with an index exists for them.

Given the PR's stated goal of bounding formation cost at scale (x50 population), confirm whether bot_life_state has generated-column indexes covering partyRequest.status/priority and the objective-spot expression, or whether this query now performs a wider full-table scan on every formation tick than before.

Also applies to: 1341-1364

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/GameServer/Bot/Population/BotLifeState.js` around lines 1253 - 1279,
Verify the indexes used by the candidate query in the bot_life_state
schema/migrations, covering the partyRequest status, partyRequest priority, and
objective-spot expression referenced by objectiveSpot and stateObjectiveSpot. If
coverage is missing, add appropriate generated columns and indexes, including
the states-table aliases where required, so the widened filters and grouping
avoid full-table scans. Confirm the same optimization applies to the
corresponding query block around the second formation path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/GameServer/Bot/Population/BotLifeState.js`:
- Around line 719-761: Update updateCache in expireStalePartyRequests to inspect
the live cached.stats.partyRequest.status before applying the deferred patch,
and skip the row unless that current status is still eligible for expiration
(open). Keep the existing snapshot validation and SQL update behavior unchanged,
but prevent stale rows excluded by the UPDATE WHERE clause from overwriting
newer cache state.

In `@src/GameServer/Bot/Population/PopulationService.js`:
- Around line 947-951: Update the objective-member selection in the population
service to require an active/open request status alongside priority ===
'required', excluding deferred requests. Preserve the fallback through
partyObjectiveForState(member) and leader so an available member with an open
objective is selected before creating the party objective and spot.
- Around line 1229-1237: Update the nearby candidate filter so the
!claimed.has(Number(state.characterId)) check wraps the entire
objective-versus-spot ternary, rather than being combined with its condition.
Preserve the existing objective matching and spot matching branches while
ensuring claimed candidates are always excluded, regardless of whether
partyObjective exists.
- Around line 876-887: Update formationWork so expireStalePartyRequests failures
are caught before finally, allowing the candidate-count and candidate-query
stages to continue; preserve scheduling nextPartyRequestCleanupAt in finally.
Change coldPartyCandidateCount in formationWork to omit the argument or pass
false so partyWaitBacklog reflects all candidates.

---

Nitpick comments:
In `@src/GameServer/Bot/AI/GearAcquisitionPlanner.js`:
- Around line 459-475: Simplify the unpreparedSupport condition in
partyNeedAssessmentForSource by removing the redundant readiness.hasWeapon check
and retaining only the support-role and armor-count conditions; leave the
separate missing_weapon return unchanged.
- Around line 627-630: Update the source assessment logic in the planner block
around the status object and its corresponding next-source block to compute the
party-need assessment once per source, then derive soloSafe, partyNeed,
partyNeedReason, and requiresParty from that shared result. Reuse the same
assessment for both the current source and next source paths, or export and
reuse the underlying assessment helper, avoiding repeated combatReadiness scans
while preserving existing field values.

In `@src/GameServer/Bot/Population/BotLifeState.js`:
- Around line 1253-1279: Verify the indexes used by the candidate query in the
bot_life_state schema/migrations, covering the partyRequest status, partyRequest
priority, and objective-spot expression referenced by objectiveSpot and
stateObjectiveSpot. If coverage is missing, add appropriate generated columns
and indexes, including the states-table aliases where required, so the widened
filters and grouping avoid full-table scans. Confirm the same optimization
applies to the corresponding query block around the second formation path.

In `@src/GameServer/Bot/Population/PopulationService.js`:
- Around line 206-208: Remove the unused partyRequestActive function from
PopulationService.js, leaving the surrounding party request logic and exports
unchanged.

In `@tests/test_bot_background_party_recruitment.js`:
- Around line 86-98: Extend the recruitment tests around
groupPartyCandidatesByObjective with two active parties sharing a spot and
neither having stats.objective, then verify each candidate is assigned exactly
once across both parties. Exercise recruitBackgroundMembers and its claimed-set
handling so the regression exposes the precedence defect without changing the
existing objective-separation or craft NPC assertions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b9bab6f3-d35d-4df7-a56f-135aae8ae684

📥 Commits

Reviewing files that changed from the base of the PR and between 1d200bf and 35e67a6.

📒 Files selected for processing (14)
  • src/GameServer/Bot/AI/GearAcquisitionPlanner.js
  • src/GameServer/Bot/Economy/CraftTelemetry.js
  • src/GameServer/Bot/Population/BackgroundPartyState.js
  • src/GameServer/Bot/Population/BotLifeState.js
  • src/GameServer/Bot/Population/PersonaPartyPolicy.js
  • src/GameServer/Bot/Population/PopulationConfig.js
  • src/GameServer/Bot/Population/PopulationMetrics.js
  • src/GameServer/Bot/Population/PopulationService.js
  • src/GameServer/Bot/Population/PopulationStatus.js
  • tests/test_bot_background_party_recruitment.js
  • tests/test_bot_craft_telemetry.js
  • tests/test_bot_gear_acquisition.js
  • tests/test_bot_party_wait.js
  • tests/test_bot_population_state.js

Comment thread src/GameServer/Bot/Population/BotLifeState.js
Comment thread src/GameServer/Bot/Population/PopulationService.js
Comment thread src/GameServer/Bot/Population/PopulationService.js
Comment thread src/GameServer/Bot/Population/PopulationService.js
@pmbstyle
pmbstyle marked this pull request as ready for review August 1, 2026 21:12

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/GameServer/Bot/AI/GearAcquisitionPlanner.js (1)

459-475: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add regression tests for equipment-based party reasons.

Add cases for a weaponless bot and a healer or buffer with fewer than two equipped armor pieces. Assert both the need value and the exact reason.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/GameServer/Bot/AI/GearAcquisitionPlanner.js` around lines 459 - 475, Add
regression tests covering partyNeedAssessmentForSource for a weaponless bot and
for an unprepared healer or buffer with fewer than two armor pieces. Assert the
exact required need value and corresponding reasons missing_weapon and
unprepared_support, while preserving existing readiness and level-margin test
coverage.
tests/test_bot_background_party_recruitment.js (1)

95-100: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert that recruitment uses both active parties.

The current assertions prove that each candidate is assigned once. They do not prove that bgp_shared_a and bgp_shared_b both receive a candidate. Assert the assigned party IDs as well.

Proposed assertion
     assert.deepStrictEqual(sharedAssignments.map((entry) => entry.characterId).sort((a, b) => a - b), [60, 61]);
+    assert.deepStrictEqual(
+        sharedAssignments.map((entry) => entry.partyId).sort(),
+        ['bgp_shared_a', 'bgp_shared_b'],
+        'each active party must receive one candidate'
+    );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_bot_background_party_recruitment.js` around lines 95 - 100, Extend
the assertions in the shared-candidate recruitment test to verify that both
active parties, bgp_shared_a and bgp_shared_b, appear in the resulting
sharedAssignments party IDs. Preserve the existing candidate uniqueness and
character ID assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@database/sql/sqlite.sql`:
- Around line 181-199: The JSON-expression indexes
bot_life_state_party_request_filter and bot_life_state_party_objective_spot are
created before legacy statsJson validation, causing initialization to fail on
malformed data. Remove these index definitions from the initial schema load and
create them in an appropriate schema migration after repairing or rejecting
invalid non-null statsJson rows; preserve both index expressions for validated
data.

---

Nitpick comments:
In `@src/GameServer/Bot/AI/GearAcquisitionPlanner.js`:
- Around line 459-475: Add regression tests covering
partyNeedAssessmentForSource for a weaponless bot and for an unprepared healer
or buffer with fewer than two armor pieces. Assert the exact required need value
and corresponding reasons missing_weapon and unprepared_support, while
preserving existing readiness and level-margin test coverage.

In `@tests/test_bot_background_party_recruitment.js`:
- Around line 95-100: Extend the assertions in the shared-candidate recruitment
test to verify that both active parties, bgp_shared_a and bgp_shared_b, appear
in the resulting sharedAssignments party IDs. Preserve the existing candidate
uniqueness and character ID assertions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 29fc52e8-901e-4736-b6ba-837b37654329

📥 Commits

Reviewing files that changed from the base of the PR and between 35e67a6 and 89e266e.

📒 Files selected for processing (5)
  • database/sql/sqlite.sql
  • src/GameServer/Bot/AI/GearAcquisitionPlanner.js
  • src/GameServer/Bot/Population/BotLifeState.js
  • src/GameServer/Bot/Population/PopulationService.js
  • tests/test_bot_background_party_recruitment.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/GameServer/Bot/Population/BotLifeState.js
  • src/GameServer/Bot/Population/PopulationService.js

Comment thread database/sql/sqlite.sql
Comment on lines +181 to +199
CREATE INDEX IF NOT EXISTS bot_life_state_party_request_filter
ON bot_life_state(
phase,
partyId,
activity,
json_extract(statsJson, '$.partyRequest.status'),
json_extract(statsJson, '$.partyRequest.priority')
);
CREATE INDEX IF NOT EXISTS bot_life_state_party_objective_spot
ON bot_life_state(
phase,
partyId,
activity,
COALESCE(
json_extract(statsJson, '$.partyRequest.spotId'),
json_extract(statsJson, '$.equipmentPlan.next.spotId'),
spotId
)
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

db=${1:?Usage: $0 /path/to/bot.sqlite}

sqlite3 "$db" <<'SQL'
SELECT sqlite_version() AS sqlite_version;
SELECT json_extract('{"probe":1}', '$.probe') AS json1_probe;

SELECT COUNT(*) AS invalid_stats_json
FROM bot_life_state
WHERE statsJson IS NOT NULL
  AND json_valid(statsJson) = 0;

PRAGMA index_list('bot_life_state');
SQL

rg -n -C 6 \
  'sqlite\.sql|CREATE INDEX IF NOT EXISTS|migration' \
  --glob '*.js' \
  --glob '*.sql' \
  .

Repository: pmbstyle/L2Solo

Length of output: 202


🏁 Script executed:

set -euo pipefail
printf '%s\n' '--- candidate files ---'
git ls-files | rg '(^|/)(database/sql/sqlite\.sql|.*(migration|schema|sqlite).*\.(js|ts|sql))$' | head -200
printf '%s\n' '--- schema context ---'
sed -n '150,215p' database/sql/sqlite.sql
printf '%s\n' '--- schema runner references ---'
rg -n -C 5 'database/sql/sqlite\.sql|sqlite\.sql|CREATE INDEX IF NOT EXISTS|executescript|migration' . \
  --glob '*.js' --glob '*.ts' --glob '*.mjs' --glob '*.cjs' --glob '*.sql' --glob '*.json' \
  | head -300
printf '%s\n' '--- SQLite tooling ---'
command -v sqlite3 || true
sqlite3 --version 2>/dev/null || true

Repository: pmbstyle/L2Solo

Length of output: 27493


🏁 Script executed:

set -euo pipefail
printf '%s\n' '--- migration implementation ---'
sed -n '175,215p' src/Database.js
printf '%s\n' '--- runtime declarations ---'
rg -n -C 4 '"engines"|node:sqlite|better-sqlite|sqlite' package.json package-lock.json npm-shrinkwrap.json 2>/dev/null | head -200 || true
printf '%s\n' '--- behavioral SQLite probe ---'
python3 - <<'PY'
import sqlite3

print("sqlite_version", sqlite3.sqlite_version)
con = sqlite3.connect(":memory:")
for sql in [
    "SELECT json_extract('{\"probe\":1}', '$.probe')",
    "SELECT json_valid('{\"probe\":1}')",
    "SELECT json_valid('{bad}')",
]:
    try:
        print(sql, "=>", con.execute(sql).fetchone()[0])
    except Exception as e:
        print(sql, "=> ERROR:", type(e).__name__, str(e))

con.executescript("""
CREATE TABLE bot_life_state (
    phase TEXT NOT NULL DEFAULT 'cold',
    partyId TEXT,
    activity TEXT NOT NULL DEFAULT 'hunting',
    spotId TEXT,
    statsJson TEXT
);
INSERT INTO bot_life_state(statsJson) VALUES
    ('{\"partyRequest\":{\"status\":\"open\",\"priority\":3}}'),
    (NULL),
    ('{bad}');
""")
for name, expression in [
    ("party_request_filter", """phase, partyId, activity,
        json_extract(statsJson, '$.partyRequest.status'),
        json_extract(statsJson, '$.partyRequest.priority')"""),
    ("party_objective_spot", """phase, partyId, activity,
        COALESCE(
            json_extract(statsJson, '$.partyRequest.spotId'),
            json_extract(statsJson, '$.equipmentPlan.next.spotId'),
            spotId
        )"""),
]:
    try:
        con.execute(f"CREATE INDEX {name} ON bot_life_state({expression})")
        print("CREATE INDEX", name, "=> success")
    except Exception as e:
        print("CREATE INDEX", name, "=> ERROR:", type(e).__name__, str(e))
PY

Repository: pmbstyle/L2Solo

Length of output: 12506


🏁 Script executed:

set -euo pipefail
printf '%s\n' '--- statsJson writes and JSON guards ---'
rg -n -C 4 'statsJson|json_valid|json_extract|JSON\.parse' src tests database --glob '*.js' --glob '*.sql' | head -400
printf '%s\n' '--- startup order ---'
sed -n '235,270p' src/Database.js
printf '%s\n' '--- schema migration metadata references ---'
rg -n -C 5 'schema_migrations|applySchemaMigrations|connection\.exec\(fs\.readFileSync' src scripts tests --glob '*.js'

Repository: pmbstyle/L2Solo

Length of output: 40524


Validate legacy statsJson before creating these indexes.

Database.init() creates these indexes before applySchemaMigrations(). If any existing non-null statsJson value is malformed, SQLite raises malformed JSON and initialization fails. Move index creation into a migration that repairs or rejects invalid rows first, or clean the data before loading database/sql/sqlite.sql.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@database/sql/sqlite.sql` around lines 181 - 199, The JSON-expression indexes
bot_life_state_party_request_filter and bot_life_state_party_objective_spot are
created before legacy statsJson validation, causing initialization to fail on
malformed data. Remove these index definitions from the initial schema load and
create them in an appropriate schema migration after repairing or rejecting
invalid non-null statsJson rows; preserve both index expressions for validated
data.

@pmbstyle
pmbstyle merged commit 1771b99 into main Aug 1, 2026
4 checks passed
@pmbstyle
pmbstyle deleted the agent/party-formation-performance branch August 1, 2026 21:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant