"use strict"; function buildArchiveRows(family) { return liveFamilyComponents(family); } function lineageEdgeFamily(family = world.family || {}) { return family || {}; } let lineageRelationIndexCache = { family: null, version: null, index: null }; function lineageBuildRelationIndex(family) { const known = new Set(Object.keys(family || {})); const parentsByChild = new Map(); const childrenByParent = new Map(); const addParent = (childId, parentId) => { if (!childId || !parentId || childId === parentId || !known.has(childId) || !known.has(parentId)) return; if (!parentsByChild.has(childId)) parentsByChild.set(childId, new Set()); if (!childrenByParent.has(parentId)) childrenByParent.set(parentId, new Set()); parentsByChild.get(childId).add(parentId); childrenByParent.get(parentId).add(childId); }; for (const n of Object.values(family || {})) { if (!n?.id) continue; for (const parentId of n.parents || []) addParent(n.id, parentId); for (const childId of n.children || []) addParent(childId, n.id); } return { parentsByChild, childrenByParent }; } function lineageRelationIndex(family = world.family || {}) { family = lineageEdgeFamily(family); if (family !== (world.family || {})) return lineageBuildRelationIndex(family); const version = world.familyVersion || 0; if (lineageRelationIndexCache.family !== family || lineageRelationIndexCache.version !== version) { lineageRelationIndexCache = { family, version, index: lineageBuildRelationIndex(family) }; } return lineageRelationIndexCache.index; } function lineageMutualParentIds(n, family = world.family || {}, idSet = null) { const seen = new Set(); const parents = []; if (!n?.id) return parents; const relationIndex = lineageRelationIndex(family); const candidates = new Set((n.parents || []).filter(Boolean)); // Runtime updates can briefly touch one side first; recover the reciprocal // edge from the relation index so the archive does not flicker. for (const parentId of relationIndex.parentsByChild.get(n.id) || []) candidates.add(parentId); for (const id of candidates) { if (!id || id === n.id || seen.has(id)) continue; if (idSet && !idSet.has(id)) continue; const parent = family[id]; if (!parent) continue; const childListsParent = Array.isArray(n.parents) && n.parents.includes(id); const parentListsChild = Array.isArray(parent.children) && parent.children.includes(n.id); if (!childListsParent && !parentListsChild) continue; seen.add(id); parents.push(id); } parents.sort((a, b) => lineageNodeSort(family[a], family[b])); return parents; } function lineageMutualChildIds(n, family = world.family || {}, idSet = null) { const seen = new Set(); const children = []; if (!n?.id) return children; const relationIndex = lineageRelationIndex(family); const candidates = new Set((n.children || []).filter(Boolean)); // Same recovery as above, in the opposite direction. for (const childId of relationIndex.childrenByParent.get(n.id) || []) candidates.add(childId); for (const id of candidates) { if (!id || id === n.id || seen.has(id)) continue; if (idSet && !idSet.has(id)) continue; const child = family[id]; if (!child) continue; const parentListsChild = Array.isArray(n.children) && n.children.includes(id); const childListsParent = Array.isArray(child.parents) && child.parents.includes(n.id); if (!parentListsChild && !childListsParent) continue; seen.add(id); children.push(id); } children.sort((a, b) => lineageNodeSort(family[a], family[b])); return children; } function lineageHasActualRelation(n, family = world.family || {}) { return lineageMutualParentIds(n, family).length > 0 || lineageMutualChildIds(n, family).length > 0; } function lineageRelatedIdSet(family) { family = lineageEdgeFamily(family); const relationIndex = lineageRelationIndex(family); const ids = new Set(); for (const n of Object.values(family || {}).filter(Boolean)) { const parents = relationIndex.parentsByChild.get(n.id) || []; const children = relationIndex.childrenByParent.get(n.id) || []; if (parents.size || children.size) ids.add(n.id); for (const id of parents) ids.add(id); for (const id of children) ids.add(id); } return ids; } function liveFamilyComponents(family) { // Childless pairs, including pairs currently performing the birth ritual, are // intentionally excluded. A \u5bb6\u7cfb\u56f3 node is created only after an actual parent/child // relation exists; otherwise ritual participants become isolated pseudo-families. family = lineageEdgeFamily(family); const relatedIds = lineageRelatedIdSet(family); const nodes = Array.from(relatedIds).map(id => family[id]).filter(Boolean); const ids = new Set(nodes.map(n => n.id)); const comps = window.TarinaiFamilyGraph.connectedComponents(nodes, { sort: lineageNodeSort, edgesOf: (n) => [ ...lineageMutualParentIds(n, family, ids), ...lineageMutualChildIds(n, family, ids), ], filter: comp => comp.some(x => x.alive), }); comps.sort((a, b) => { const ag = Math.min(...a.map(n => n.generation || 1)); const bg = Math.min(...b.map(n => n.generation || 1)); const at = Math.min(...a.map(n => n.birthTime || 0)); const bt = Math.min(...b.map(n => n.birthTime || 0)); return ag - bg || at - bt || b.length - a.length; }); return comps; }