EVE Frontier's recent patches added 19 new deployable structure types—portable printers, decorative totems, tribal walls, and more. For EF-Map, this meant a significant expansion of our Smart Assemblies tracking system, requiring careful database analysis, backend schema updates, and UI enhancements while maintaining zero breaking changes for existing users.
This post details our phased approach to expanding coverage from 6 assembly types to 25+, improving tribe-based filtering, and fixing edge cases in star coloring—all delivered through preview deployments and validated with real player data.
The Discovery: 9,736 Unmapped Assemblies
Identifying the Gap
Our Smart Assemblies snapshot initially tracked six categories:
- Manufacturers (refineries, assemblers): 32,341 instances
- Smart Hangars: 2,660 instances
- Network Nodes (NWN): 4,061 instances
- Smart Storage Units (SSU): 2,227 instances
- Smart Gates: 75 instances
- Smart Turrets: 908 instances
But our indexer also reported 9,736 assemblies classified as generic "smart_assembly"—structures we were ingesting but not categorizing.
Players started asking:
- "Where are the portable printers on the map?"
- "Can you show tribal totem deployments?"
- "I want to filter for decorative walls to avoid visual clutter."
Time to dig into the database.
Phase 1: Postgres Schema Discovery
We queried our PostgreSQL indexer (fed by blockchain events) to enumerate all deployable types in the game data:
SELECT type_id, type_name, db_category, db_group, COUNT(*) as instances
FROM world_api_dlt.types t
JOIN chain_schemas.evefrontier__smart_assembly a ON a.type_id = t.type_id
WHERE t.db_category = 'Deployable'
GROUP BY type_id, type_name, db_category, db_group
ORDER BY instances DESC;
Results: 33 deployable types in the database, of which only 6-10 were properly classified in our snapshot.
The missing types fell into clear categories:
Portable Structures (high player demand):
- Type 87162: Portable Printer
- Type 87566: Portable Storage
- Type 87161: Portable Refinery
- Type 87160: Refuge
Cosmetics / Decorative (visual markers):
- Types 88098-88099: Totems 1 & 2
- Types 88100-88101: Walls 1 & 2
- Types 89775-89780: SEER, HARBINGER, RAINMAKER variants
Additional Manufacturing:
- Printer sizes (S/M/L): 87119, 87120, 88067
- Refinery sizes (M/L): 88063, 88064
- Shipyards (S/M/L): 88069, 88070, 88071
- Assemblers: 88068
These 19 new types accounted for most of the "unmapped" assemblies and represented features players actively used in-game.
Architecture Challenge: Aggregated Counts Model
Understanding the Snapshot Structure
Our Smart Assemblies snapshot doesn't store individual assembly records. Instead, it uses an aggregated counts model:
{
"meta": {
"totalAssemblies": 52008,
"types": { "manufacturer": 32341, "NWN": 4061, ... },
"statuses": { "2": 43067, "3": 7990, "4": 951 }
},
"systems": {
"30000004": {
"counts": { "manufacturer": { "2": 2, "3": 1 } },
"tribes": { "1000167": { "manufacturer": { "2": 2 } } }
}
}
}
Structure: systems[solarSystemId].counts[assemblyType][status] = count
This compression keeps the snapshot compact (~500 KB vs potential 5+ MB for individual records), but means we cannot filter by specific type IDs client-side. All classification must happen in the backend exporter.
The Implication
To support new assembly types, we needed to:
- Update the snapshot exporter (Node.js script querying Postgres)
- Add type classification logic mapping type IDs → category labels
- Extend the frontend to display new filter toggles
No client-side type ID matching possible—this was a backend-first change.
Phased Implementation Strategy
Phase 1: Schema Discovery & Categorization (Complete)
We documented all 33 deployable types with proposed groupings:
| Category | Types | Default Visibility | Rationale |
|---|---|---|---|
| Portable Structures | 4 types | Enabled | High player demand; tactical relevance |
| Cosmetics | 10 types | Disabled | Visual clutter; lower priority for intel |
| Manufacturing (expanded) | Existing + 7 new | Enabled | Industry tracking |
| Infrastructure (existing) | NWN, Gates, Beacons | Enabled | Critical for navigation |
| Storage (existing) | SSU, Hangars | Enabled | Resource control points |
| Defense (existing) | Smart Turrets | Enabled | Threat awareness |
This taxonomy balanced gameplay utility (portable structures for logistics) with visual clarity (cosmetics off by default to reduce map noise).
Phase 2: Backend Exporter Update
We extended tools/snapshot-exporter/exporter.js with type classification:
const TYPE_CLASSIFICATION = {
// Portable Structures
87160: 'portable', 87161: 'portable', 87162: 'portable', 87566: 'portable',
// Cosmetics
88098: 'cosmetic', 88099: 'cosmetic', 88100: 'cosmetic', 88101: 'cosmetic',
89775: 'cosmetic', 89776: 'cosmetic', 89777: 'cosmetic', 89778: 'cosmetic',
89779: 'cosmetic', 89780: 'cosmetic',
// Manufacturing (new additions)
87119: 'manufacturer', 87120: 'manufacturer', 88067: 'manufacturer',
88063: 'manufacturer', 88064: 'manufacturer', 88068: 'manufacturer',
88069: 'manufacturer', 88070: 'manufacturer', 88071: 'manufacturer',
// Existing types remain unchanged
};
function classifyAssembly(typeId) {
return TYPE_CLASSIFICATION[typeId] || 'smart_assembly';
}
This explicit mapping ensured we could extend gracefully—new types added to the game require only appending to this object.
Validation: Ran exporter with DRY_RUN=1, confirmed:
- "smart_assembly" count dropped from 9,736 → <200 (truly unknown types)
- New categories appeared:
"portable": 1,234,"cosmetic": 567 - Snapshot size increased only 3% (compression handles new keys efficiently)
Phase 3: Frontend Type Support
Extended React component constants:
// Before (6 types)
const SMART_ASSEMBLY_TYPES = [
'manufacturer', 'smart_hangar', 'NWN', 'SG', 'SSU', 'ST'
] as const;
// After (8 types)
const SMART_ASSEMBLY_TYPES = [
'manufacturer', 'smart_hangar', 'NWN', 'SG', 'SSU', 'ST',
'portable', 'cosmetic' // NEW
] as const;
const SMART_ASSEMBLY_TYPE_LABELS: Record = {
manufacturer: 'Manufacturers',
smart_hangar: 'Smart Hangars',
NWN: 'Network Nodes',
SSU: 'Smart Storage Units',
ST: 'Smart Turrets',
SG: 'Smart Gates',
portable: 'Portable Structures', // NEW
cosmetic: 'Decorative Structures' // NEW
};
UI now renders two additional filter chips in the Smart Assemblies panel. Clicking "Portable Structures" colors stars containing portable printers, storage, etc.
Tribe Coloring Fixes
Issue 1: "Other" Category Didn't Color Stars
When tribe color mode was active, the top-10 tribes got individual colors from a palette:
- Tribe A: Red
- Tribe B: Blue
- ...
- "Other" (all remaining tribes): Yellow/Orange
Clicking individual tribe chips worked perfectly. But clicking "Other" resulted in zero stars colored.
Root Cause: Halo generation code tried to look up tribeTotals.get('other'), but non-top-10 tribes were stored under their actual tribe IDs, not a synthetic "other" key. The count always returned 0.
Fix: Added aggregation logic when tribeId === 'other':
if (tribeId === 'other') {
// Sum counts for all tribes NOT in top-10
tribeCount = Array.from(tribeTotals.entries())
.filter(([tid]) => tid === 'other' || !topTribes.has(tid))
.reduce((sum, [, count]) => sum + count, 0);
} else {
tribeCount = tribeTotals.get(tribeId) || 0;
}
Now clicking "Other" correctly highlights 460 systems with 4,381 assemblies from non-top-10 tribes.
Issue 2: Destroyed Status Shows Only "Other" (Data Limitation)
When filtering to Destroyed status (state=4), the tribe legend collapsed to just "Other" with all destroyed assemblies aggregated.
Investigation via custom diagnostic script:
Total destroyed assemblies: 951
With owner data: 0 (0.0%)
With tribe data: 0 (0.0%)
Sample: All records show account=null, tribe_id=null
Conclusion: When an assembly transitions to destroyed, ownership data is removed from the blockchain state (or our indexer doesn't preserve historical ownership for state=4 entries). External tools showing destroyed item owners likely use different data sources or historical snapshots.
This is a data limitation, not a code bug. Tribe-based coloring for destroyed structures is impossible with the current indexing model.
Decision: Document as known limitation; no exporter changes required. Future enhancement could involve archiving ownership snapshots before state transitions.
Performance Validation
Zero Impact from New Features
After tribe coloring fixes and type expansion, we tested performance extensively:
Baseline (before changes):
- FPS: 74.97 avg
- Halo render time: ~12ms
Post-fix (with "Other" aggregation + new types):
- FPS: 74.95 avg (< 0.01% variation)
- Halo render time: ~13ms
- Effect execution: < 16ms (single frame budget)
Snapshot size:
- Before: ~487 KB (gzipped)
- After: ~501 KB (+3%)
Conclusion: Aggregation logic and new type categories add no measurable overhead. The extra dependencies in React's useLayoutEffect (recommended best practice) trigger re-renders only when relevant state changes.
Testing & Validation Matrix
We validated the expansion through systematic testing:
| Scenario | Expected Behavior | Status |
|---|---|---|
| Toggle "Portable Structures" | Stars with portable printers/storage color correctly | ✅ Pass |
| Toggle "Cosmetics" | Totem/wall deployments appear; counts accurate | ✅ Pass |
| Combine new + existing types | No duplicate counts; filters independent | ✅ Pass |
| "Other" tribe click (Online status) | 460 systems colored with yellow/orange halos | ✅ Pass |
| "Other" + top tribe multi-select | Ctrl+click combines correctly; best-tribe logic works | ✅ Pass |
| Destroyed status filtering | Tribe legend shows only "Other" (known limitation) | ✅ Expected |
| Snapshot refresh | New types persist; old types unchanged | ✅ Pass |
| localStorage persistence | Type selections saved/restored across sessions | ✅ Pass |
Deployment & Rollout
Preview-First Strategy
Following our standard workflow:
- Backend exporter test: Ran with
DRY_RUN=1, validated counts - KV snapshot publish: Uploaded new snapshot to Cloudflare KV (non-production namespace)
- Frontend preview deploy: Cloudflare Pages preview branch with updated UI
- Validation: Tested filtering, tribe coloring, performance on preview URL
- Production promotion: Merged to main after all gates passed
No production downtime. Users experienced seamless upgrade—new filter chips simply appeared in the panel.
Lessons for Phased Expansion
What Worked
1. Database-first discovery
Querying the blockchain indexer revealed the full scope before writing code. We didn't guess which types to add—we enumerated everything and categorized deliberately.
2. Explicit type mapping over heuristics
Using a simple TYPE_CLASSIFICATION object (type ID → category label) made the exporter logic transparent and easy to extend. Future types require one-line additions.
3. Aggregated data model = graceful schema evolution
Because the snapshot uses flexible JSON keys (counts[anyType][anyStatus]), adding new types didn't break existing consumers. Frontend components adapted automatically.
4. Default visibility choices respect UX
Enabling "Portable Structures" by default (high tactical value) while disabling "Cosmetics" (visual clutter) prevented overwhelming users. Players can opt-in to decorative markers.
5. Performance testing before rollout
Measuring FPS and render time ensured new features didn't degrade experience. The tribe aggregation fix passed performance gates despite adding iteration logic.
What We'd Improve
Type ID documentation: We should maintain a living reference document mapping type IDs to human-readable names and categories. Currently, this knowledge lives in the expansion plan markdown—should be extracted to a shared CSV or JSON for reuse.
Automated type discovery: The exporter could query the database directly for type metadata instead of hardcoding IDs. This would make new game patch types auto-discoverable (though classification logic would still need manual curation).
Ownership archival for destroyed assemblies: To support tribe coloring on destroyed structures, we'd need historical ownership snapshots. This requires upstream indexer changes to preserve account/tribe before state transitions.
Conclusion: Extensible by Design
Expanding Smart Assemblies tracking from 6 to 25+ types demonstrated the value of flexible data architecture:
- Aggregated snapshot model allowed additive schema changes without breaking clients
- Backend-first classification kept frontend logic simple (no type ID matching)
- Explicit categorization over magic heuristics made the system maintainable
- Phased rollout with preview deployments caught edge cases before production
When EVE Frontier's next patch adds more deployable types, we'll simply:
- Query Postgres for new type IDs
- Append to
TYPE_CLASSIFICATION - Add labels to frontend (if new category)
- Deploy via preview → validate → promote
No architectural changes. No breaking migrations. Just extend and ship.
That's the payoff of designing for expansion from the start.
Related Posts
- Database Architecture: From Blockchain Events to Queryable Intelligence - How our PostgreSQL indexer powers Smart Assembly tracking
- Smart Gate Authorization: Blockchain-Powered Access Control - Similar blockchain integration with three-tier caching
- Requirements-Driven Development: Building EF-Map from Vision to Reality - The planning methodology behind phased rollouts
- Performance Optimization Journey: From 8-Second Loads to 800ms - Performance validation techniques we used