Phase 2.3: Cloud Functions Updated with TEI Grades & Hysteresis
Summary
Updated all Cloud Functions to calculate and store displayGrade with hysteresis support.
Changes Made
1. Rating Types (functions/src/tei/rating-types.ts)
Added displayGrade field:
export interface StoredRating {
readonly mu: number;
readonly sigma: number;
readonly matches: number;
readonly displayRating: number; // μ - 3σ
readonly displayGrade?: TeiGrade; // NEW: E/V/C/I/P with hysteresis
}
Added helper function:
export function toStoredRatingWithGrade(
rating: { mu: number; sigma: number; matches: number },
previousRating?: StoredRating
): StoredRating
This function:
- Imports
getTeiDisplayfromwarp12-engine - Reads
previousRating.displayGradefor hysteresis - Calculates new TEI grade with hysteresis
- Returns
StoredRatingwith newdisplayGrade
2. Human Rating Updates (apply-human-tei.ts)
Changed:
// Before
const ratingAfter = toStoredRating({
...newRating,
matches: ratingBefore.matches + 1,
});
// After
const ratingAfter = toStoredRatingWithGrade(
{
...newRating,
matches: ratingBefore.matches + 1,
},
ratingBefore // Pass previous rating for hysteresis
);
Effect: Online human vs human matches now calculate and store TEI grades
3. Group/Charter Rating Updates (apply-group-tei.ts)
Same change as #2 - group/crew ratings now include displayGrade
4. Solo AI Practice (report-practice-ai.ts)
Same change as #2 - solo practice vs AI now includes displayGrade
5. Exports Updated
rating-types.ts: ExportstoStoredRatingWithGraderated-match-schema.ts: ExportstoStoredRatingWithGradetei/index.ts: ExportstoStoredRatingWithGrade
How Hysteresis Works
- First Match (new player):
previousRatingisundefinedor has nodisplayGradegetTeiDisplay(newRating, undefined)uses raw thresholds- Result stored with
displayGrade(e.g., “V”)
- Subsequent Matches:
- Read
previousRating.displayGradefrom Firestore getTeiDisplay(newRating, previousRating.displayGrade)applies hysteresis- Different enter/exit thresholds prevent flickering
- New
displayGradesaved to Firestore
- Read
- Example Flow:
// Match 1: Player σ = 1.5 (new) getTeiDisplay({ mu: 28, sigma: 1.5, matches: 1 }, undefined) // → grade: 'C' (raw threshold: 1.5 ≤ σ < 2.5) // Saved: displayGrade = 'C' // Match 2: Player σ = 1.42 (improving) getTeiDisplay({ mu: 29, sigma: 1.42, matches: 2 }, 'C') // → grade: 'C' (hysteresis: stays C until σ < 1.4) // Saved: displayGrade = 'C' // Match 3: Player σ = 1.35 (crossed threshold) getTeiDisplay({ mu: 30, sigma: 1.35, matches: 3 }, 'C') // → grade: 'V' (crossed V enter threshold at 1.4) // Saved: displayGrade = 'V' ← PROMOTION!
Database Schema
Firestore playerStats/{uid}:
{
uid: "abc123",
displayName: "Alice",
humanRating: {
goOut: {
rating: {
mu: 32.0,
sigma: 1.2,
matches: 150,
displayRating: 28.4, // μ - 3σ
displayGrade: "V" // NEW: Hysteresis-stable grade
},
wins: 87
},
points: {
rating: {
mu: 28.5,
sigma: 2.1,
matches: 120,
displayRating: 22.2,
displayGrade: "C" // NEW
},
wins: 65
}
},
localAi: {
commander: {
goOut: {
rating: {
mu: 30.0,
sigma: 2.8,
matches: 45,
displayRating: 21.6,
displayGrade: "I" // NEW
},
wins: 22
}
}
}
}
Build Status
✅ Functions build successfully
- All TypeScript compiles without errors
- Vendor packages staged correctly
- Ready for deployment
Files Modified
Core Functions:
- ✅
functions/src/tei/rating-types.ts- AddeddisplayGrade,toStoredRatingWithGrade() - ✅
functions/src/tei/apply-human-tei.ts- UsestoStoredRatingWithGrade() - ✅
functions/src/tei/apply-group-tei.ts- UsestoStoredRatingWithGrade() - ✅
functions/src/report-practice-ai.ts- UsestoStoredRatingWithGrade()
Exports:
- ✅
functions/src/tei/rated-match-schema.ts- Exports new function - ✅
functions/src/tei/index.ts- Exports new function
Testing Checklist
Before deployment, test:
- New player first match:
- Verify
displayGradeis set (should be P, I, or C depending on starting σ) - Verify grade uses raw thresholds (no hysteresis)
- Verify
- Existing player matches:
- Verify
displayGradeupdates with hysteresis - Verify grade doesn’t flicker at boundaries (σ ≈ 1.5, 2.5, 4.0)
- Verify
- Grade promotions:
- Player at C with σ dropping from 2.4 → 1.35 should upgrade to V
- Verify old grade was used in calculation
- All match types:
- Online human vs human (via
apply-human-tei) - Group/charter matches (via
apply-group-tei) - Solo practice vs AI (via
report-practice-ai)
- Online human vs human (via
Deployment
# Build functions
cd functions && npm run build
# Deploy to Firebase
cd .. && yarn deploy:functions
# Or deploy everything
yarn deploy:firebase
Integration with UI
The UI components created in Phase 3 expect displayGrade to be present:
<TeiDisplay
rating={playerStats.humanRating.goOut.rating}
currentGrade={playerStats.humanRating.goOut.rating.displayGrade} // ← Required
objective="goOut"
/>
Without displayGrade, the components will use raw thresholds (which is fine for first display, but won’t have hysteresis).
Next Steps
- Deploy functions to staging/production
- Wipe Firestore collections (user confirmed safe - no production data)
- Test end-to-end rating updates
- Phase 3 Integration: Connect UI components to backend data
- Update stats service to read
displayGradefrom Firestore - Update profile/leaderboard pages to use new components
Status: ✅ Phase 2.3 Complete - Functions ready for deployment
Build: ✅ All functions compile successfully
Hysteresis: ✅ Fully integrated into rating calculations
Ready for: Phase 3 UI integration + deployment