A mobile app for ranking fighting game characters through competitive tier lists. Challenge your friends to settle which character truly belongs at the top!
Rivalry Club is a React Native mobile app where users compete to build the ultimate tier list for fighting games. Instead of simply ranking characters, you face off against your rivals in head-to-head "contests" where characters from your tier lists battle it out. Winners rise, losers fall, and over time the best tier list emerges.
- Select a Game: Choose from Super Smash Bros Ultimate and other fighting games
- Create a Rivalry: Challenge another user to compete in the same game
- Contests: The app randomly selects fighters from both tier lists to face off
- Ranking System:
- Each contest adjusts fighter positions based on the outcome
- Characters start at the bottom (position 85) as "Unknown Tier"
- Through contests, fighters earn their place in the tier list
- Winning moves a fighter up, losing moves them down
- Tier Lists: Your rankings organize into tiers (S, A, B, C, etc.) based on position
- Standing: Track your overall record against rivals
- Anonymous Play: Start playing immediately without account creation
- Optional Account Linking: Create an account later to save progress across devices
- Profile System: View your rivalries, manage account settings
- Pending Rivalries: Accept or decline incoming rivalry challenges
- Contest History: Review past matchups and see how your tier list evolved
- Individual Reshuffle: Don't like one of the matchup fighters? Reshuffle just that side!
- Node.js 18+ installed
- iOS Simulator (for iOS development) or Android Studio (for Android development)
- AWS credentials configured (for backend deployment)
- Expo CLI (
npm install -g expo-cli)
# Install dependencies
npm install
# iOS: Install pods
cd ios && npx pod-install && cd ..
# Run on iOS
npm run ios
# Run on Android
npm run androidFor testing production builds, use:
- Email:
t@t.t - Password:
12345678
Note: After running the password reset script, all production users have password qwerqwer
Build and submit to TestFlight in one command:
npm run build:ios:localThis will:
- Increment the build number automatically
- Build the iOS app locally
- Submit to App Store Connect
- Move the
.ipafile toios_builds/directory
View on TestFlight: The build appears in ~15 minutes after completion.
App Store Details:
- Bundle ID:
club.rivalry.app - Apple ID:
davidmoritz@gmail.com - ASC App ID:
6740737367 - Team ID:
FVG6A24S96
Build for Android:
npm run build:androidAfter the build completes:
- Download from Expo Dashboard
- Upload to Google Play Console
- Create a new release and share the Play Store link with testers
App Details:
- Package:
club.rivalry.app - Current Version:
1.3.0 - Version Code:
1
rivalry-club-expo/
├── src/
│ ├── components/
│ │ ├── common/ # Reusable UI components
│ │ │ ├── Button.tsx
│ │ │ ├── CharacterDisplay.tsx
│ │ │ ├── LoadingSpinner.tsx
│ │ │ └── ...
│ │ ├── screens/ # Screen-level components
│ │ │ ├── Home.tsx # Game selection
│ │ │ ├── Auth.tsx # Sign in/sign up
│ │ │ ├── RivalryIndex.tsx # Rivalries list
│ │ │ ├── ConnectedRivalryView.tsx # Active rivalry view
│ │ │ ├── Profile.tsx # User profile & settings
│ │ │ └── parts/ # Screen sub-components
│ │ │ ├── CurrentContest.tsx
│ │ │ ├── RivalryView.tsx
│ │ │ ├── TierListDisplay.tsx
│ │ │ └── ...
│ ├── controllers/ # React Query hooks for data
│ │ ├── c-rivalry.ts # Rivalry queries & mutations
│ │ ├── c-user.ts # User queries
│ │ └── c-fighter.ts # Fighter queries
│ ├── models/ # Extended GraphQL types
│ │ ├── m-game.ts
│ │ ├── m-rivalry.ts
│ │ ├── m-contest.ts
│ │ ├── m-tier-list.ts
│ │ ├── m-tier-slot.ts
│ │ ├── m-fighter.ts
│ │ └── m-user.ts
│ ├── providers/ # React Context providers
│ │ ├── game.ts # Selected game context
│ │ ├── rivalry.tsx # Current rivalry context
│ │ └── all-rivalries.tsx
│ ├── graphql/ # Custom GraphQL operations
│ ├── lib/ # Core libraries
│ │ ├── amplify-auth.ts # Cognito authentication
│ │ └── amplify-data.ts # Data client setup
│ ├── utils/ # Utility functions & styles
│ └── axios/ # REST API client
├── amplify/ # AWS Amplify Gen 2 backend
│ ├── auth/ # Cognito configuration
│ ├── data/ # GraphQL schema & resolvers
│ │ └── resource.ts # Schema definition
│ └── backend.ts # Backend configuration
├── scripts/ # Utility scripts
│ ├── create-test-user.js # Create Cognito users
│ ├── reset-cognito-passwords.js # Reset all passwords
│ └── increment-build.js # Auto-increment build numbers
├── assets/ # Images, icons, fighter sprites
├── App.tsx # App entry point
└── app.json # Expo configuration
- React Native
0.81.5- Mobile framework - Expo
~54.0- Development platform - TypeScript
~5.9- Type safety - React Navigation - Native stack navigation
- React Query
^5.90- Data fetching & caching
- AWS Amplify Gen 2 - Backend infrastructure
- Cognito - User authentication
- AppSync - GraphQL API with custom resolvers for atomic operations
- DynamoDB - Database with atomic increment support
- S3 - Asset storage
- GraphQL - API query language
- Atomic Increments - Race-condition-free counter updates via AppSync JavaScript resolvers (see
ai_reports/atomic-increment-implementation.md)
- React Native StyleSheet - Component styling
- Inline styles - Dynamic styling
- React Context - Global state (Game, Rivalry)
- React Query - Server state
- React Hooks - Local component state
- Jest - Testing framework
- ESLint - Code linting
- Patch-package - NPM package patching
- EAS Build - Cloud builds
- AWS SDK - Backend scripts
The app uses AWS Cognito for authentication with a flexible approach:
- Users can start playing immediately without creating an account
- Progress is stored locally on the device
- Perfect for trying out the app
- Optional: Users can create an account at any time via the Profile screen
- Accounts preserve progress across devices
- Email/password authentication
- Session management with automatic token refresh
- No Auth Required: Users start in the app immediately after game selection
- Optional Link: "Link Account" option in Profile screen
- Sign In/Sign Up: Custom auth UI (
Auth.tsx) - Session Persistence: Secure token storage via Expo SecureStore
Auth Library: src/lib/amplify-auth.ts
signIn()- Email/password sign insignUp()- Create new accountsignOut()- End sessiongetCurrentUser()- Get authenticated userconfirmSignUp()- Email verificationresetPassword()- Password reset flow
User Record: After authentication, the app automatically creates a User record in DynamoDB with:
id- UUIDemail- User's emailawsSub- Cognito user IDfirstName,lastName- Optional display namesrole- User role (0 = standard user)
Game
- Fighting game (e.g., "Super Smash Bros. Ultimate")
- Has many Fighters and Rivalries
Fighter
- Character in a game
- Tracks aggregate stats across all users
- Has game position, contest count, win count, tier breakdown
User
- App user account
- Links to Cognito via
awsSub - Can have multiple Rivalries and TierLists
Rivalry
- Competition between two users in a specific game
- Tracks contest count, current contest, accepted status
- Each user has one TierList for this rivalry
TierList
- User's ranking of all fighters in a game (for a specific rivalry)
- Has 86 TierSlots (one per fighter in SSBU)
- Tracks standing (wins/losses against rival)
TierSlot
- Single fighter's position in a TierList
- Position: 0-85 (0 = top, 85 = bottom) or null (unknown)
- Tracks contestCount and winCount for this fighter in this tier list
Contest
- Single matchup between two fighters from rival tier lists
- Stores result (positive = user A won, negative = user B won)
- Stores bias (tier difference between fighters)
The app extends GraphQL types with computed properties and utility methods:
Models: src/models/m-*.ts
- Prefix
Mindicates extended model (e.g.,MRivalry) - Add computed properties (e.g.,
game.abbr,tierList.prestigeDisplay) - Add utility methods (e.g.,
rivalry.adjustStanding(),tierList.sampleEligibleSlot())
When a contest is resolved:
- Update Contest Record: Store result and bias in database
- Adjust Standings: Update each TierList's
standingbased on outcome - Update Fighter Positions:
- Winner moves up by
result × STEPS_PER_STOCK × -1positions - Loser moves down by
result × STEPS_PER_STOCKpositions - Unknown fighters (position = null) are positioned at bottom first
- Winner moves up by
- Update Fighter Stats: Increment global stats for both fighters
- Create New Contest: Automatically sample next matchup
Unknown Fighter Positioning (positionUnknownFighter):
- Places fighter at target position
- If occupied, finds first empty slot going UP (towards position 0)
- Shifts consecutive fighters UP by 1 position (85→84, 84→83, etc.)
- Used when fighters are positioned or repositioned
Bottom Positioning (positionFighterAtBottom):
- Places fighter at position 85 (bottom)
- If occupied, finds first empty slot going UP
- Shifts consecutive fighters UP by 1 position (85→84, 84→83)
- Used when moving fighters to the bottom during reshuffle
Collision Handling: Both methods shift UP (towards position 0) and only shift consecutive occupied positions, preserving gaps in the tier list.
When selecting fighters for a contest (sampleEligibleSlot):
- Filter to "eligible" slots:
- Must have a position (not null)
- contestCount < 20 (fighters need more data), OR
- winCount / contestCount between 0.35 and 0.65 (competitive fighters)
- If no eligible slots, broaden criteria to include all positioned slots
- Randomly sample from eligible set
Individual reshuffle (new in v1.3):
- User clicks reshuffle button for one side of the contest
- NEW fighter is sampled and keeps its current position (whatever it is, including null)
- OLD fighter moves to position 85 using
positionFighterAtBottom() - Other side remains unchanged
- Prevents seeing the same matchup repeatedly
amplify_outputs.json- Auto-generated Amplify configuration (development)amplify_outputs.production.json- Production Amplify configuration- App automatically uses production config when built with
eas build
# Create test user in Cognito + DynamoDB
node scripts/create-test-user.js user@example.com --first=John --last=Doe
# Confirm/reset user password
node scripts/confirm-user.js user@example.com --password=newpass
# Reset ALL production user passwords to 'qwerqwer'
node scripts/reset-cognito-passwords.js# Increment iOS build number
node scripts/increment-build.js
# Find DynamoDB tables
node scripts/find-tables.jsSee scripts/README.md for detailed documentation.
# Run all tests
npm test
# Run tests in watch mode
npm test:watch
# Generate coverage report
npm test:coverageDeploy schema changes to AWS:
# Deploy to production
npm run amplify:deploy
# Start local sandbox (development)
npm run amplify:sandboxNote: Schema changes require backend deployment before the app can use new fields.
- Open iOS Simulator manually (don't use
npm run ios) - Swipe left on home screen
- Click the "Rivalry Club" app icon
The React Native packager isn't running:
# Start the packager manually
sh node_modules/react-native/scripts/launchPackager.commandThen press "i" to run on iOS, or reload in the simulator.
Set the correct Xcode path:
sudo xcode-select -s /Applications/Xcode.app/Contents/DeveloperDisable Flipper and reinstall pods:
cd ios
NO_FLIPPER=1 npx pod-install
cd ..- Deploy backend changes:
npm run amplify:deploy - Restart TypeScript server in your IDE
- Clear watchman cache if needed:
watchman watch-del-all
Check Cognito user status:
# List all users in production pool
aws cognito-idp list-users \
--user-pool-id us-east-1_8f6RCLauy \
--region us-east-1
# Check specific user
aws cognito-idp admin-get-user \
--user-pool-id us-east-1_8f6RCLauy \
--username user@example.com \
--region us-east-1- Developer Guide: See
CLAUDE.mdfor detailed architecture and conventions - Scripts Documentation: See
scripts/README.mdfor utility scripts - AI Reports: See
ai_reports/index.mdfor technical implementation reports
Fighter images are organized by game:
src/assets/images/games/
└── ssbu/ # Super Smash Bros. Ultimate
├── bayonetta.png
├── bowser.png
├── captain-falcon.png
└── ... (86 fighters total)
Icons use FontAwesome (configured in src/assets/icons.ts).
- Individual reshuffle buttons for each contest slot
- Improved bottom positioning with upward collision shifting
- Profile screen enhancements
- Password reset script for production users
- Contest history view
- Tier list visualization improvements
- Profile management system
- Bug fixes and performance improvements
- Anonymous user support
- Account linking functionality
- Rivalry creation and management
- Contest system implementation
- Initial release
- Basic rivalry functionality
- Tier list system
- AWS Amplify Gen 2 integration
This is a private project. For questions or issues, contact the development team.
Copyright © 2025 Rivalry Club. All rights reserved.