Designing a Scalable Community Content System
Problem Statement
During development of the Ballistics puzzle game, I built a comprehensive level editing toolset for creating physics-based puzzle levels. This toolset enables efficient placement of game objects, configuration of gameplay parameters, and rapid iteration on level design. The natural progression was to democratize this capability—enabling players to create and share their own levels while maintaining quality standards and preventing abuse.
The core challenge involved designing a system that would allow user-generated content while addressing several critical constraints:
- Ensuring published levels meet quality standards (solvability requirement)
- Preventing statistical inconsistencies when levels are modified post-publication
- Implementing efficient content discovery with multiple query patterns
- Mitigating cost concerns through strategic resource gating
- Preventing reward system abuse through infinite farming exploits
User Workflow and Feature Design
Content Creation and Management
Upon accessing the Community Levels interface from the main menu, users are presented with their draft levels and have access to community-published content. The workflow supports three primary operations:
- Create New Level: Initializes a blank level in draft status
- Duplicate Existing Level: Creates a copy of any level the user owns
- Delete Level: Removes levels from the user's collection
Edit Mode Constraints
When editing a level, users have access to place up to 50 instances of each game object type, with one critical exception: coins are limited to 5 per level. This constraint exists because achieving three-star completion requires collecting all coins, and an excessive number would create unreasonable difficulty spikes that harm the player experience.
Configuration System
Level configuration occurs through a dedicated interface where creators specify:
- Available Components: Quantity limits for each object type (tracks, portals, speed tracks, trampolines)
- Component Tiers: Maximum upgrade tier available for each component type, aligning with the game's progression system
- Active Skills: Up to three player-activated abilities with corresponding level specifications
- Cannon Parameters: Power level and trajectory prediction accuracy
- Visual Theme: Background selection for aesthetic customization
Quality Assurance Through "Play to Publish"
Validation Requirements
A fundamental design principle: every published level must be completable. To enforce this, I implemented a "Play to Publish" validation system. After configuring a level, creators must achieve three-star completion (all balls reaching targets, all coins collected) before the publish action becomes available.
The validation flow operates as follows:
- Creator clicks "Play to Publish," triggering an instantaneous mode transition from edit to play state
- All editing controls are removed; the level functions identically to standard gameplay
- Upon achieving three-star completion, the publish button activates
- Creator provides a level name and confirms publication
- System displays a warning regarding the immutability of published levels
Rationale for Immutability
Published levels are immutable by design. This decision addresses two critical data integrity concerns:
- Statistical Validity: Levels accumulate metadata (likes, attempts, completion rates). Allowing post-publication edits would invalidate these metrics, particularly in scenarios where a level could be simplified after accumulating positive statistics or made more difficult after recording successful completions.
- Player Progress Preservation: Individual player statistics (completion status, best time, star ratings) are tracked per level UUID. Modifications would invalidate these records, creating inconsistencies in player progression data.
To maintain flexibility, users can duplicate published levels, make modifications, delete the original, and publish the updated version—preserving iterative design capabilities while maintaining data integrity.
Data Architecture and Storage Strategy
DynamoDB Schema Design
I designed a DynamoDB schema to support multiple query patterns efficiently. The primary table structure uses:
- Partition Key:
user_id(Apple ID) - Sort Key:
creation_time(integer timestamp)
Stored attributes include:
uuid: Level identifier (UUID4)status: Either "draft" or "published"level_name: User-provided string (converted to lowercase)display_name: Creator's public usernamelikes: Integer count of player endorsementsattempts: Integer count of play attemptsrating: Calculated popularity score (see algorithm below)level: MapAttribute containing complete level JSON data
Global Secondary Indexes
To support efficient content discovery, I implemented two GSIs:
GSI 1: status-score-index
- Partition Key:
status - Sort Key:
rating
This index enables retrieval of published levels ordered by popularity score, supporting the default "Top Levels" view.
GSI 2: level-name-score-index
- Partition Key:
level_name - Sort Key:
rating
This index supports name-based search using DynamoDB's begins_with operator on the partition key, enabling efficient prefix matching without requiring a full table scan. All level names are stored in lowercase to ensure case-insensitive search behavior.
Supported Query Patterns
The schema supports four primary access patterns:
- User's Own Levels: Query on primary key (
user_id) returns all levels owned by the creator - Top Published Levels: Query GSI 1 with
status = "published", sorted by rating in descending order - Search by Name: Query GSI 2 with
begins_withonlevel_name, sorted by rating - Levels by Creator: Query primary table with specific
user_idand filter onstatus = "published"(acceptable performance given the 50-level-per-user maximum)
Scoring Algorithm Design
Complexity Trade-offs
The ideal scoring system would normalize metrics across all levels, comparing each level's statistics against global maxima. However, maintaining global maximum values (highest like count, most attempts, most completions) would require either:
- Scanning the entire table on each update (unacceptable cost and latency)
- Maintaining a separate aggregation table with complex synchronization logic
Logarithmic Scoring Approach
I opted for a logarithmic scoring algorithm that provides relative ranking without requiring global state tracking. The formula applies logarithmic scaling to three engagement metrics:
rating = (log₂(likes) × α) + (log₃(attempts) × β) + (log₅(wins) × γ)
Where:
likes: Number of player endorsementsattempts: Total play attemptswins: Successful completions (one or more stars)α, β, γ: Tunable weight coefficients for balancing metric influence
The logarithmic approach provides several advantages:
- Diminishing returns prevent single metrics from dominating rankings
- Different logarithm bases enable fine-tuning of metric relative importance
- Calculation requires only local data, eliminating cross-record dependencies
- The algorithm naturally handles edge cases (log of zero/small numbers)
Cost Optimization and Abuse Prevention
Energy-Based Rate Limiting
To prevent excessive iteration and reduce infrastructure costs, each "Play to Publish" attempt requires five energy units. This economic gate ensures creators invest meaningful effort in level design before validation attempts, reducing frivolous submissions and DynamoDB write operations.
Auto-Save Implementation
To balance user experience with cost management, I implemented an auto-save system with the following characteristics:
- Save Interval: Two-minute periodic saves
- Change Detection: Client-side JSON comparison against last saved state
- Conditional Updates: DynamoDB writes occur only when content has changed
This approach provides crash protection (maximum two minutes of lost work) while minimizing unnecessary write operations to the database.
Reward System Design
Analysis revealed a significant abuse vector: if coin collection in community levels credited to player profiles, users could create trivial levels with five coins in close proximity, complete them repeatedly, delete and recreate the level, and farm coins indefinitely.
To mitigate this risk, community level completion rewards exclusively provide gems (premium currency not tied to progression gates). Coins remain obtainable only through standard game levels, preserving the progression economy's integrity.
Capacity Management
Users have a default creation limit of five community levels. This limit is expandable to 50 levels through in-app purchase, providing a monetization pathway while preventing database bloat from abandoned content. The low default limit also reduces the performance impact of filter operations when querying published levels by creator.
Lessons Learned
This implementation highlighted several key architectural insights:
- Immutability Simplifies State Management: Preventing level edits post-publication eliminated complex data migration scenarios and statistical recalculation logic
- Economic Gates Influence Behavior: Energy costs naturally encourage thoughtful iteration rather than trial-and-error approaches
- Client-Side Filtering Can Be Acceptable: The 50-level-per-user cap makes client-side filtering viable for specific query patterns
- Logarithmic Scoring Scales Well: The scoring algorithm provides meaningful rankings without requiring global state synchronization
- Abuse Prevention Requires Economic Thinking: Understanding player incentives revealed the coin farming exploit before production deployment
Future Considerations
Areas for potential enhancement include:
- Implementing a reporting system for inappropriate content
- Adding tag-based categorization for improved content discovery
- Developing difficulty estimation algorithms based on completion statistics
- Creating featured level curation workflows for highlighting exceptional community content
Comments
Post a Comment