1/**2 * Turn-local indexes for the rule engine.3 *4 * The engine mutates object positions during a turn, so this index is rebuilt5 * at each settled rule boundary. Consumers must treat the maps as read-only6 * and rebuild after replacing the object array or changing a noun.7 */8export function buildIndexes(state, rules, semanticProperties) {9 const objectsById = new Map();10 const objectsByCell = new Map();11 const rulesBySubject = new Map();12 const objectsByProperty = new Map();1314 for (const object of state.objects) {15 objectsById.set(object.id, object);16 const cell = `${object.x},${object.y}`;17 const occupants = objectsByCell.get(cell);18 if (occupants) occupants.push(object);19 else objectsByCell.set(cell, [object]);20 }21 for (const rule of rules) {22 const subjectRules = rulesBySubject.get(rule.subject);23 if (subjectRules) subjectRules.push(rule);24 else rulesBySubject.set(rule.subject, [rule]);25 }2627 const propertiesByObjectId = new Map();28 for (const object of state.objects) {29 if (object.kind === 'word') {30 propertiesByObjectId.set(object.id, new Set(['PUSH']));31 continue;32 }33 const properties = new Set(34 (rulesBySubject.get(object.noun) || [])35 .filter(rule => semanticProperties.has(rule.predicate))36 .map(rule => rule.predicate),37 );38 propertiesByObjectId.set(object.id, properties);39 for (const property of properties) {40 const carriers = objectsByProperty.get(property);41 if (carriers) carriers.push(object);42 else objectsByProperty.set(property, [object]);43 }44 }4546 return { objectsById, objectsByCell, objectsByProperty, rulesBySubject, propertiesByObjectId };47}