Improve spear equipment icon silhouettes
This commit is contained in:
@@ -59,7 +59,11 @@ function parseIconDefinitions(source, drawFunctions) {
|
||||
functionName: drawMatch[1],
|
||||
colors: [...drawMatch[2].matchAll(/0x[0-9a-fA-F]{6}/g)].map((colorMatch) => colorMatch[0].toLowerCase())
|
||||
}));
|
||||
const colors = [...new Set(drawCalls.flatMap((call) => call.colors))].sort();
|
||||
const colors = [
|
||||
...new Set(
|
||||
drawCalls.flatMap((call) => [...call.colors, ...(drawFunctions.get(call.functionName)?.colors ?? [])])
|
||||
)
|
||||
].sort();
|
||||
const primitiveCount = drawCalls.reduce((sum, call) => sum + (drawFunctions.get(call.functionName)?.primitiveCount ?? 0), 0);
|
||||
const lineNumber = source.slice(0, match.index).split(/\r?\n/).length;
|
||||
|
||||
@@ -210,16 +214,13 @@ function parseSceneUsage(source) {
|
||||
function collectItemRows(itemCatalog, equipmentSlots, iconDefinitions) {
|
||||
const slotOrder = new Map(equipmentSlots.map((slot, index) => [slot, index]));
|
||||
|
||||
return Object.entries(itemCatalog)
|
||||
const rows = Object.entries(itemCatalog)
|
||||
.map(([itemId, item]) => {
|
||||
const icon = iconDefinitions.get(itemId);
|
||||
const concerns = [];
|
||||
if (!icon) {
|
||||
concerns.push('missing generated icon');
|
||||
} else {
|
||||
if (icon.drawCalls.length <= 1) {
|
||||
concerns.push('single helper silhouette');
|
||||
}
|
||||
if (icon.primitiveCount < 18) {
|
||||
concerns.push('low primitive detail');
|
||||
}
|
||||
@@ -240,17 +241,24 @@ function collectItemRows(itemCatalog, equipmentSlots, iconDefinitions) {
|
||||
colorCount: icon?.colors.length ?? 0,
|
||||
concerns
|
||||
};
|
||||
})
|
||||
.sort((left, right) => {
|
||||
const slotDiff = (slotOrder.get(left.slot) ?? 99) - (slotOrder.get(right.slot) ?? 99);
|
||||
if (slotDiff !== 0) {
|
||||
return slotDiff;
|
||||
}
|
||||
if (left.rank !== right.rank) {
|
||||
return left.rank === 'treasure' ? -1 : 1;
|
||||
}
|
||||
return left.itemId.localeCompare(right.itemId);
|
||||
});
|
||||
const signatureCounts = countBy(rows.filter((row) => row.icon), (row) => row.signature);
|
||||
rows.forEach((row) => {
|
||||
if (row.icon && row.drawCalls <= 1 && (signatureCounts.get(row.signature) ?? 0) > 1) {
|
||||
row.concerns.unshift('shared helper silhouette');
|
||||
}
|
||||
});
|
||||
|
||||
return rows.sort((left, right) => {
|
||||
const slotDiff = (slotOrder.get(left.slot) ?? 99) - (slotOrder.get(right.slot) ?? 99);
|
||||
if (slotDiff !== 0) {
|
||||
return slotDiff;
|
||||
}
|
||||
if (left.rank !== right.rank) {
|
||||
return left.rank === 'treasure' ? -1 : 1;
|
||||
}
|
||||
return left.itemId.localeCompare(right.itemId);
|
||||
});
|
||||
}
|
||||
|
||||
function groupBySignature(itemRows) {
|
||||
@@ -298,29 +306,61 @@ function groupRiskScore(rows) {
|
||||
}
|
||||
|
||||
function buildRecommendations(itemRows, groupedRows) {
|
||||
const weaponPolearmIds = itemRows
|
||||
.filter((row) => row.slot === 'weapon' && ['drawSpearIcon', 'drawPolearmIcon'].some((name) => row.drawFunctions.includes(name)))
|
||||
.map((row) => row.itemId);
|
||||
const weaponPolearmTargetIds = [
|
||||
'green-dragon-glaive',
|
||||
'serpent-spear',
|
||||
'iron-spear',
|
||||
'sky-piercer-halberd',
|
||||
'western-cavalry-spear'
|
||||
];
|
||||
const weaponPolearmRows = itemRows.filter((row) => weaponPolearmTargetIds.includes(row.itemId));
|
||||
const armorIds = itemRows.filter((row) => row.slot === 'armor').map((row) => row.itemId);
|
||||
const accessoryIds = itemRows.filter((row) => row.slot === 'accessory').map((row) => row.itemId);
|
||||
const weaponPolearmComplete =
|
||||
weaponPolearmRows.length === weaponPolearmTargetIds.length &&
|
||||
weaponPolearmRows.every((row) => row.icon && row.primitiveCount >= 18) &&
|
||||
new Set(weaponPolearmRows.map((row) => row.signature)).size === weaponPolearmRows.length;
|
||||
|
||||
const completedTargets = weaponPolearmComplete
|
||||
? [
|
||||
{
|
||||
title: 'weapon spear/polearm family',
|
||||
itemIds: weaponPolearmRows.map((row) => row.itemId),
|
||||
reason: 'The previous shared spear/polearm silhouettes now use item-specific helper shapes with enough primitive detail for 20px and 30px equipment UI reads.'
|
||||
}
|
||||
]
|
||||
: [];
|
||||
|
||||
return {
|
||||
firstTargetTitle: 'weapon spear/polearm family',
|
||||
firstTargetIds: weaponPolearmIds,
|
||||
firstTargetReason:
|
||||
'These are high-frequency equipment surfaces and several treasure/common weapons share a thin diagonal shaft silhouette; upgrading this group gives the clearest identity gain in sortie prep and equipment swap screens.',
|
||||
followUps: [
|
||||
{
|
||||
title: 'armor silhouettes',
|
||||
itemIds: armorIds,
|
||||
reason: 'Armor and robes are readable but depend on simple triangular torsos and small color accents.'
|
||||
},
|
||||
{
|
||||
title: 'accessory charms and tokens',
|
||||
itemIds: accessoryIds,
|
||||
reason: 'Accessories are the smallest UI read and several use compact badge-like shapes.'
|
||||
}
|
||||
],
|
||||
completedTargets,
|
||||
firstTargetTitle: weaponPolearmComplete ? 'armor silhouettes' : 'weapon spear/polearm family',
|
||||
firstTargetIds: weaponPolearmComplete ? armorIds : weaponPolearmRows.map((row) => row.itemId),
|
||||
firstTargetReason: weaponPolearmComplete
|
||||
? 'Armor and robes are readable but still depend on simple triangular torsos and small color accents; this is now the largest remaining shared-equipment silhouette group.'
|
||||
: 'These are high-frequency equipment surfaces and several treasure/common weapons share a thin diagonal shaft silhouette; upgrading this group gives the clearest identity gain in sortie prep and equipment swap screens.',
|
||||
firstTargetSuccessCriteria: weaponPolearmComplete
|
||||
? 'Each armor item should keep its slot identity while gaining a distinct outer silhouette and material cue at 20px and 30px.'
|
||||
: 'Each item should read as a distinct weapon silhouette at 20px and 30px, with treasure weapons visibly richer than common weapons.',
|
||||
followUps: weaponPolearmComplete
|
||||
? [
|
||||
{
|
||||
title: 'accessory charms and tokens',
|
||||
itemIds: accessoryIds,
|
||||
reason: 'Accessories are the smallest UI read and several use compact badge-like shapes.'
|
||||
}
|
||||
]
|
||||
: [
|
||||
{
|
||||
title: 'armor silhouettes',
|
||||
itemIds: armorIds,
|
||||
reason: 'Armor and robes are readable but depend on simple triangular torsos and small color accents.'
|
||||
},
|
||||
{
|
||||
title: 'accessory charms and tokens',
|
||||
itemIds: accessoryIds,
|
||||
reason: 'Accessories are the smallest UI read and several use compact badge-like shapes.'
|
||||
}
|
||||
],
|
||||
topRiskGroups: groupedRows.slice(0, 5)
|
||||
};
|
||||
}
|
||||
@@ -356,7 +396,8 @@ function renderReport({ iconSourceSize, iconTextureSize, itemRows, groupedRows,
|
||||
'- Treasure/common distinction often relies on UI frame color plus a small palette change, not a strong unique silhouette.',
|
||||
'- Reused helper silhouettes are the strongest quality risk because several different items collapse to the same shape at 14-22px.',
|
||||
'',
|
||||
'## First Improvement Target',
|
||||
...renderCompletedTargets(recommendations.completedTargets),
|
||||
'## Next Improvement Target',
|
||||
'',
|
||||
`- Target: ${recommendations.firstTargetTitle}`,
|
||||
`- Item ids: ${recommendations.firstTargetIds.map((itemId) => `\`${itemId}\``).join(', ')}`,
|
||||
@@ -397,14 +438,31 @@ function renderReport({ iconSourceSize, iconTextureSize, itemRows, groupedRows,
|
||||
'',
|
||||
'## Recommended Next Batch Definition',
|
||||
'',
|
||||
'- Scope: replace or enrich the five spear/polearm weapon icons while keeping `item-${itemId}` texture keys stable.',
|
||||
'- Success criteria: each item reads as a distinct weapon silhouette at 20px and 30px, with treasure weapons visibly richer than common weapons.',
|
||||
`- Scope: improve ${recommendations.firstTargetTitle} while keeping \`item-\${itemId}\` texture keys stable.`,
|
||||
`- Item ids: ${recommendations.firstTargetIds.map((itemId) => `\`${itemId}\``).join(', ')}`,
|
||||
`- Success criteria: ${recommendations.firstTargetSuccessCriteria}`,
|
||||
'- Keep the existing 64px texture size unless a UI-wide equipment icon pass is planned.',
|
||||
'- Add before/after contact sheets and verify the sortie equipment list and equipment swap panel in a desktop browser.',
|
||||
''
|
||||
].join('\n')}`;
|
||||
}
|
||||
|
||||
function renderCompletedTargets(completedTargets) {
|
||||
if (!completedTargets.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
'## Completed Improvement Passes',
|
||||
'',
|
||||
...completedTargets.flatMap((target) => [
|
||||
`- ${target.title}: ${target.itemIds.map((itemId) => `\`${itemId}\``).join(', ')}`,
|
||||
` - ${target.reason}`
|
||||
]),
|
||||
''
|
||||
];
|
||||
}
|
||||
|
||||
function countBy(rows, keySelector) {
|
||||
return rows.reduce((counts, row) => {
|
||||
const key = keySelector(row);
|
||||
|
||||
Reference in New Issue
Block a user