Filter inclusion groups by timed effects (#2765) * Filter inclusion groups by timed effects Closes #2762 * Skip group scoring check if sticky entries are present * Optimize sticky checks

75c6bee350c65b4f1824cc399edff8feb9d532d1

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
1 files changed, +71 -4Ignore whitespace
public/scripts/world-info.js+71 -4
@@ -3953,7 +3953,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
3953 let newContent = '';3953 let newContent = '';
3954 const textToScanTokens = await getTokenCountAsync(allActivatedText);3954 const textToScanTokens = await getTokenCountAsync(allActivatedText);
39553955
3956 filterByInclusionGroups(newEntries, allActivatedEntries, buffer, scanState);3956 filterByInclusionGroups(newEntries, allActivatedEntries, buffer, scanState, timedEffects);
39573957
3958 console.debug('[WI] --- PROBABILITY CHECKS ---');3958 console.debug('[WI] --- PROBABILITY CHECKS ---');
3959 for (const entry of newEntries) {3959 for (const entry of newEntries) {
@@ -4143,8 +4143,9 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
4143 * @param {WorldInfoBuffer} buffer The buffer to use for scoring4143 * @param {WorldInfoBuffer} buffer The buffer to use for scoring
4144 * @param {(entry: WIScanEntry) => void} removeEntry The function to remove an entry4144 * @param {(entry: WIScanEntry) => void} removeEntry The function to remove an entry
4145 * @param {number} scanState The current scan state4145 * @param {number} scanState The current scan state
4146 * @param {Map<string, boolean>} hasStickyMap The sticky entries map
4146 */4147 */
4147function filterGroupsByScoring(groups, buffer, removeEntry, scanState) {4148function filterGroupsByScoring(groups, buffer, removeEntry, scanState, hasStickyMap) {
4148 for (const [key, group] of Object.entries(groups)) {4149 for (const [key, group] of Object.entries(groups)) {
4149 // Group scoring is disabled both globally and for the group entries4150 // Group scoring is disabled both globally and for the group entries
4150 if (!world_info_use_group_scoring && !group.some(x => x.useGroupScoring)) {4151 if (!world_info_use_group_scoring && !group.some(x => x.useGroupScoring)) {
@@ -4152,6 +4153,13 @@ function filterGroupsByScoring(groups, buffer, removeEntry, scanState) {
4152 continue;4153 continue;
4153 }4154 }
41544155
4156 // If the group has any sticky entries, the rest are already removed by the timed effects filter
4157 const hasAnySticky = hasStickyMap.get(key);
4158 if (hasAnySticky) {
4159 console.debug(`[WI] Skipping group scoring check, group '${key}' has sticky entries`);
4160 continue;
4161 }
4162
4155 const scores = group.map(entry => buffer.getScore(entry, scanState));4163 const scores = group.map(entry => buffer.getScore(entry, scanState));
4156 const maxScore = Math.max(...scores);4164 const maxScore = Math.max(...scores);
4157 console.debug(`[WI] Group '${key}' max score:`, maxScore);4165 console.debug(`[WI] Group '${key}' max score:`, maxScore);
@@ -4176,13 +4184,64 @@ function filterGroupsByScoring(groups, buffer, removeEntry, scanState) {
4176}4184}
41774185
4178/**4186/**
4187 * Removes entries on cooldown and forces sticky entries as winners.
4188 * @param {Record<string, WIScanEntry[]>} groups The groups to filter
4189 * @param {WorldInfoTimedEffects} timedEffects The timed effects to use
4190 * @param {(entry: WIScanEntry) => void} removeEntry The function to remove an entry
4191 * @returns {Map<string, boolean>} If any sticky entries were found
4192 */
4193function filterGroupsByTimedEffects(groups, timedEffects, removeEntry) {
4194 /** @type {Map<string, boolean>} */
4195 const hasStickyMap = new Map();
4196
4197 for (const [key, group] of Object.entries(groups)) {
4198 hasStickyMap.set(key, false);
4199
4200 // If the group has any sticky entries, leave only the sticky entries
4201 const stickyEntries = group.filter(x => timedEffects.isEffectActive('sticky', x));
4202 if (stickyEntries.length) {
4203 for (const entry of group) {
4204 if (stickyEntries.includes(entry)) {
4205 continue;
4206 }
4207
4208 console.debug(`[WI] Entry ${entry.uid}`, `removed as a non-sticky loser from inclusion group '${key}'`, entry);
4209 removeEntry(entry);
4210 }
4211
4212 hasStickyMap.set(key, true);
4213 }
4214
4215 // It should not be possible for an entry on cooldown/delay to event get into the grouping phase but @Wolfsblvt told me to leave it here.
4216 const cooldownEntries = group.filter(x => timedEffects.isEffectActive('cooldown', x));
4217 if (cooldownEntries.length) {
4218 console.debug(`[WI] Inclusion group '${key}' has entries on cooldown. They will be removed.`, cooldownEntries);
4219 for (const entry of cooldownEntries) {
4220 removeEntry(entry);
4221 }
4222 }
4223
4224 const delayEntries = group.filter(x => timedEffects.isEffectActive('delay', x));
4225 if (delayEntries.length) {
4226 console.debug(`[WI] Inclusion group '${key}' has entries with delay. They will be removed.`, delayEntries);
4227 for (const entry of delayEntries) {
4228 removeEntry(entry);
4229 }
4230 }
4231 }
4232
4233 return hasStickyMap;
4234}
4235
4236/**
4179 * Filters entries by inclusion groups.4237 * Filters entries by inclusion groups.
4180 * @param {object[]} newEntries Entries activated on current recursion level4238 * @param {object[]} newEntries Entries activated on current recursion level
4181 * @param {Set<object>} allActivatedEntries Set of all activated entries4239 * @param {Set<object>} allActivatedEntries Set of all activated entries
4182 * @param {WorldInfoBuffer} buffer The buffer to use for scanning4240 * @param {WorldInfoBuffer} buffer The buffer to use for scanning
4183 * @param {number} scanState The current scan state4241 * @param {number} scanState The current scan state
4242 * @param {WorldInfoTimedEffects} timedEffects The timed effects currently active
4184 */4243 */
4185function filterByInclusionGroups(newEntries, allActivatedEntries, buffer, scanState) {4244function filterByInclusionGroups(newEntries, allActivatedEntries, buffer, scanState, timedEffects) {
4186 console.debug('[WI] --- INCLUSION GROUP CHECKS ---');4245 console.debug('[WI] --- INCLUSION GROUP CHECKS ---');
41874246
4188 const grouped = newEntries.filter(x => x.group).reduce((acc, item) => {4247 const grouped = newEntries.filter(x => x.group).reduce((acc, item) => {
@@ -4212,11 +4271,19 @@ function filterByInclusionGroups(newEntries, allActivatedEntries, buffer, scanSt
4212 }4271 }
4213 }4272 }
42144273
4215 filterGroupsByScoring(grouped, buffer, removeEntry, scanState);4274 const hasStickyMap = filterGroupsByTimedEffects(grouped, timedEffects, removeEntry);
4275 filterGroupsByScoring(grouped, buffer, removeEntry, scanState, hasStickyMap);
42164276
4217 for (const [key, group] of Object.entries(grouped)) {4277 for (const [key, group] of Object.entries(grouped)) {
4218 console.debug(`[WI] Checking inclusion group '${key}' with ${group.length} entries`, group);4278 console.debug(`[WI] Checking inclusion group '${key}' with ${group.length} entries`, group);
42194279
4280 // If the group has any sticky entries, the rest are already removed by the timed effects filter
4281 const hasAnySticky = hasStickyMap.get(key);
4282 if (hasAnySticky) {
4283 console.debug(`[WI] Skipping inclusion group check, group '${key}' has sticky entries`);
4284 continue;
4285 }
4286
4220 if (Array.from(allActivatedEntries).some(x => x.group === key)) {4287 if (Array.from(allActivatedEntries).some(x => x.group === key)) {
4221 console.debug(`[WI] Skipping inclusion group check, group '${key}' was already activated`);4288 console.debug(`[WI] Skipping inclusion group check, group '${key}' was already activated`);
4222 // We need to forcefully deactivate all other entries in the group4289 // We need to forcefully deactivate all other entries in the group