snes-disassembler
Version:
A Super Nintendo (SNES) ROM disassembler for 65816 assembly
399 lines • 16 kB
JavaScript
;
/**
* AI-Powered Intelligent Naming Suggestions
*
* Generates contextually appropriate names for SNES assets using:
* - Pattern recognition results
* - SNES-specific naming conventions
* - Game context analysis
* - Historical naming patterns from well-known games
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.AINameSuggester = void 0;
/**
* AI-Enhanced Naming Suggestion Engine
*/
class AINameSuggester {
constructor(configManager) {
this.configManager = configManager;
this.snesConventions = new SNESNamingConventions();
this.gameDatabase = new GameSpecificNaming();
}
/**
* Generate intelligent naming suggestions for an asset
*/
async suggestNames(context) {
const config = this.configManager.getConfig();
if (!config.namingSuggestions.enabled) {
return this.generateBasicSuggestions(context);
}
const suggestions = [];
// Generate context-aware suggestions
if (config.namingSuggestions.useContextualNames) {
suggestions.push(...await this.generateContextualSuggestions(context));
}
// Apply SNES conventions
if (config.namingSuggestions.useSNESConventions) {
suggestions.push(...this.applySNESConventions(context));
}
// Use custom patterns
if (config.namingSuggestions.customPatterns) {
suggestions.push(...this.applyCustomPatterns(context, config.namingSuggestions.customPatterns));
}
// Game-specific naming
if (context.gameId) {
suggestions.push(...this.applyGameSpecificNaming(context));
}
// Sort by confidence and remove duplicates
return this.rankAndDeduplicate(suggestions);
}
/**
* Generate contextual names using AI pattern recognition
*/
async generateContextualSuggestions(context) {
const suggestions = [];
// Graphics naming
if (context.classification?.graphics) {
const graphics = context.classification.graphics;
suggestions.push({
name: this.generateGraphicsName(graphics, context),
confidence: graphics.confidence,
reasoning: `Graphics classification detected ${graphics.type} with ${(graphics.confidence * 100).toFixed(1)}% confidence`,
category: 'sprite',
alternatives: this.generateGraphicsAlternatives(graphics, context),
conventions: ['type_based', 'size_aware']
});
}
// Audio naming
if (context.classification?.audio) {
const audio = context.classification.audio;
suggestions.push({
name: this.generateAudioName(audio, context),
confidence: audio.confidence,
reasoning: `Audio classification detected ${audio.type} with ${(audio.confidence * 100).toFixed(1)}% confidence`,
category: 'audio',
alternatives: this.generateAudioAlternatives(audio, context),
conventions: ['snes_audio', 'channel_aware']
});
}
// Text naming
if (context.classification?.text) {
const text = context.classification.text;
suggestions.push({
name: this.generateTextName(text, context),
confidence: text.confidence,
reasoning: `Text classification detected ${text.type} with ${(text.confidence * 100).toFixed(1)}% confidence`,
category: 'text',
alternatives: this.generateTextAlternatives(text, context),
conventions: ['text_type', 'encoding_aware']
});
}
return suggestions;
}
generateGraphicsName(graphics, context) {
const bankHex = context.bank.toString(16).toUpperCase().padStart(2, '0');
const offsetHex = context.offset.toString(16).toUpperCase().padStart(4, '0');
switch (graphics.type) {
case 'sprite':
return `sprite_${bankHex}_${offsetHex}`;
case 'tile':
return `tile_${graphics.format}_${bankHex}_${offsetHex}`;
case 'background':
return `bg_layer_${bankHex}_${offsetHex}`;
case 'ui':
return `ui_element_${bankHex}_${offsetHex}`;
case 'font':
return `font_${graphics.format}_${bankHex}_${offsetHex}`;
case 'palette':
return `palette_${bankHex}_${offsetHex}`;
default:
return `graphics_${bankHex}_${offsetHex}`;
}
}
generateAudioName(audio, context) {
const bankHex = context.bank.toString(16).toUpperCase().padStart(2, '0');
const offsetHex = context.offset.toString(16).toUpperCase().padStart(4, '0');
switch (audio.type) {
case 'brr_sample':
return `sample_brr_${bankHex}_${offsetHex}`;
case 'sequence':
return `music_seq_${bankHex}_${offsetHex}`;
case 'spc_code':
return `spc_driver_${bankHex}_${offsetHex}`;
case 'instrument':
return `instrument_${bankHex}_${offsetHex}`;
default:
return `audio_${bankHex}_${offsetHex}`;
}
}
generateTextName(text, context) {
const bankHex = context.bank.toString(16).toUpperCase().padStart(2, '0');
const offsetHex = context.offset.toString(16).toUpperCase().padStart(4, '0');
switch (text.type) {
case 'dialogue':
return `dialogue_${text.compression !== 'none' ? text.compression + '_' : ''}${bankHex}_${offsetHex}`;
case 'menu':
return `menu_text_${bankHex}_${offsetHex}`;
case 'item_name':
return `item_names_${bankHex}_${offsetHex}`;
case 'credits':
return `credits_text_${bankHex}_${offsetHex}`;
case 'code_comment':
return `debug_text_${bankHex}_${offsetHex}`;
default:
return `text_${bankHex}_${offsetHex}`;
}
}
generateGraphicsAlternatives(graphics, context) {
const base = this.generateGraphicsName(graphics, context);
const alternatives = [base];
// Size-based alternatives
if (graphics.dimensions) {
alternatives.push(`${graphics.type}_${graphics.dimensions.width}x${graphics.dimensions.height}_data`);
}
// Format-based alternatives
if (graphics.format) {
alternatives.push(`${graphics.type}_${graphics.format}_data`);
}
// Index-based alternatives
const assetIndex = Math.floor(context.offset / 0x1000); // Rough index
alternatives.push(`${graphics.type}_${assetIndex.toString().padStart(3, '0')}`);
return [...new Set(alternatives)];
}
generateAudioAlternatives(audio, context) {
const base = this.generateAudioName(audio, context);
const alternatives = [base];
// Rate-based alternatives
if (audio.sampleRate) {
alternatives.push(`${audio.type}_${audio.sampleRate}hz`);
}
// Channel-based alternatives
if (audio.channels) {
alternatives.push(`${audio.type}_${audio.channels}ch`);
}
// Encoding-based alternatives
if (audio.encoding) {
alternatives.push(`${audio.type}_${audio.encoding}`);
}
return [...new Set(alternatives)];
}
generateTextAlternatives(text, context) {
const base = this.generateTextName(text, context);
const alternatives = [base];
// Encoding-based alternatives
alternatives.push(`${text.type}_${text.encoding}`);
// Compression-based alternatives
if (text.compression && text.compression !== 'none') {
alternatives.push(`${text.type}_compressed`);
alternatives.push(`${text.type}_${text.compression}`);
}
return [...new Set(alternatives)];
}
applySNESConventions(context) {
return this.snesConventions.generateConventionalNames(context);
}
applyCustomPatterns(context, patterns) {
const suggestions = [];
for (const pattern of patterns) {
const name = this.applyPattern(pattern, context);
if (name) {
suggestions.push({
name,
confidence: 0.6,
reasoning: `Generated from custom pattern: ${pattern}`,
category: this.inferCategoryFromContext(context),
alternatives: [],
conventions: ['custom_pattern']
});
}
}
return suggestions;
}
applyPattern(pattern, context) {
let result = pattern;
// Replace common placeholders
const replacements = {
'{{bank}}': context.bank.toString(16).toUpperCase().padStart(2, '0'),
'{{offset}}': context.offset.toString(16).toUpperCase().padStart(4, '0'),
'{{size}}': context.size.toString(),
'{{index}}': Math.floor(context.offset / 0x1000).toString().padStart(3, '0'),
'{{type}}': context.classification?.graphics?.type || 'unknown',
'{{layer}}': '0', // Default layer
'{{asset_type}}': this.inferCategoryFromContext(context),
'{{game}}': context.gameId || 'unknown'
};
for (const [placeholder, value] of Object.entries(replacements)) {
result = result.replace(new RegExp(placeholder, 'g'), value);
}
return result;
}
applyGameSpecificNaming(context) {
return this.gameDatabase.generateGameSpecificNames(context);
}
generateBasicSuggestions(context) {
const bankHex = context.bank.toString(16).toUpperCase().padStart(2, '0');
const offsetHex = context.offset.toString(16).toUpperCase().padStart(4, '0');
return [{
name: `data_${bankHex}_${offsetHex}`,
confidence: 0.5,
reasoning: 'Basic naming based on memory location',
category: 'data',
alternatives: [
`asset_${bankHex}_${offsetHex}`,
`unknown_${bankHex}_${offsetHex}`,
`rom_${context.offset.toString(16)}`
]
}];
}
inferCategoryFromContext(context) {
if (context.classification?.graphics)
return 'sprite';
if (context.classification?.audio)
return 'audio';
if (context.classification?.text)
return 'text';
// Heuristic based on location
if (context.bank >= 0x80)
return 'code';
if (context.offset < 0x8000)
return 'data';
return 'data';
}
rankAndDeduplicate(suggestions) {
// Remove duplicates by name
const unique = new Map();
for (const suggestion of suggestions) {
const existing = unique.get(suggestion.name);
if (!existing || suggestion.confidence > existing.confidence) {
unique.set(suggestion.name, suggestion);
}
}
// Sort by confidence descending
return Array.from(unique.values())
.sort((a, b) => b.confidence - a.confidence)
.slice(0, 10); // Limit to top 10 suggestions
}
}
exports.AINameSuggester = AINameSuggester;
/**
* SNES-specific naming conventions
*/
class SNESNamingConventions {
generateConventionalNames(context) {
const suggestions = [];
// Bank-based naming
if (context.bank <= 0x3F) {
suggestions.push({
name: `lorom_bank${context.bank.toString(16).toUpperCase()}_${context.offset.toString(16)}`,
confidence: 0.7,
reasoning: 'LoROM bank naming convention',
category: 'data',
alternatives: [],
conventions: ['lorom', 'bank_based']
});
}
else if (context.bank >= 0x80 && context.bank <= 0xBF) {
suggestions.push({
name: `hirom_bank${context.bank.toString(16).toUpperCase()}_${context.offset.toString(16)}`,
confidence: 0.7,
reasoning: 'HiROM bank naming convention',
category: 'data',
alternatives: [],
conventions: ['hirom', 'bank_based']
});
}
// Size-based naming
if (context.size === 32) {
suggestions.push({
name: `tile_8x8_data_${context.offset.toString(16)}`,
confidence: 0.6,
reasoning: '32 bytes matches 4bpp 8x8 tile size',
category: 'sprite',
alternatives: ['chr_8x8_data', 'gfx_tile_data'],
conventions: ['tile_size', 'snes_graphics']
});
}
return suggestions;
}
}
/**
* Game-specific naming database
*/
class GameSpecificNaming {
constructor() {
this.gamePatterns = {
'zelda3': {
sprites: ['link_', 'enemy_', 'npc_', 'item_'],
audio: ['overworld_', 'dungeon_', 'boss_', 'sfx_'],
text: ['dialogue_', 'item_name_', 'menu_'],
commonOffsets: {
0x80000: 'link_sprite_data',
0x90000: 'enemy_sprite_data',
0xA0000: 'overworld_music'
}
},
'smw': {
sprites: ['mario_', 'enemy_', 'powerup_', 'platform_'],
audio: ['bgm_', 'sfx_', 'voice_sample_'],
text: ['level_name_', 'message_', 'credits_'],
commonOffsets: {
0x83000: 'mario_graphics',
0x94000: 'level_music'
}
}
};
}
generateGameSpecificNames(context) {
const suggestions = [];
if (!context.gameId || !this.gamePatterns[context.gameId]) {
return suggestions;
}
const pattern = this.gamePatterns[context.gameId];
// Check for known offsets
if (pattern.commonOffsets[context.offset]) {
suggestions.push({
name: pattern.commonOffsets[context.offset],
confidence: 0.9,
reasoning: `Known ${context.gameId} asset at this location`,
category: 'sprite', // Default category
alternatives: [],
conventions: ['game_specific', context.gameId]
});
}
// Apply game-specific prefixes
const category = this.inferCategoryFromContext(context);
let prefixes = [];
switch (category) {
case 'sprite':
prefixes = pattern.sprites || [];
break;
case 'audio':
prefixes = pattern.audio || [];
break;
case 'text':
prefixes = pattern.text || [];
break;
}
for (const prefix of prefixes) {
suggestions.push({
name: `${prefix}${context.offset.toString(16)}`,
confidence: 0.75,
reasoning: `${context.gameId}-specific naming pattern`,
category,
alternatives: [],
conventions: ['game_specific', context.gameId]
});
}
return suggestions;
}
inferCategoryFromContext(context) {
if (context.classification?.graphics)
return 'sprite';
if (context.classification?.audio)
return 'audio';
if (context.classification?.text)
return 'text';
return 'data';
}
}
//# sourceMappingURL=ai-naming-suggestions.js.map