# Made in Heaven Wedding Reels
## Codex-Ready Product and Technical Specification
Version 1.0 - July 14, 2026

## 1. Purpose

Made in Heaven Wedding Reels is a wedding-themed social slot game distributed to wedding guests as both a Progressive Web App (PWA) and a Chrome extension. Guests spin three animated reels. Periodically, a server-authorized three-symbol match lets the winning guest take one to three photos. Those photos take over the matched symbol throughout every connected game.

A later winner can match that guest's symbol and replace it, creating a playful cycle in which guests get onto the reels, get kicked off, and try to win their way back. Every replacement is announced to the entire wedding and preserved in a chronological event photo history for the couple.

Three protected couple images form a special collective symbol class called **Made in Heaven**. Any three-reel combination composed entirely of those protected images counts as a Made in Heaven match. It triggers a long comic failure tone and a message explaining that the match cannot be replaced.

The product is entertainment only. It must not accept wagers, award money, or represent itself as gambling.

## 2. Product Goals

1. Give guests a simple, highly social activity that works from their own phones.
2. Generate a living, chronological photo history during the event.
3. Let guests repeatedly enter, leave, and re-enter the live reels.
4. Make every reel occupant equally likely to be selected on an authorized winning spin.
5. Let the couple control branding, sound, pacing, moderation, and final archival.
6. Support QR-code and AirBridge ultrasonic discovery without making AirBridge mandatory.
7. Use one shared web codebase for mobile browsers, installed PWA mode, desktop browsers, and a Chrome extension wrapper.

## 3. Core Terminology

- **Wedding:** One configured event and its isolated data.
- **Guest:** An anonymous or named participant with a device session.
- **Symbol identity:** One of 20 logical reel positions/classes.
- **Protected symbol class:** The three couple images, collectively treated as Made in Heaven.
- **Replaceable symbol:** One of 17 normal symbol identities that can be occupied by default art or guest photos.
- **Stop:** A physical position on a reel strip. Each reel has 40 stops.
- **Occupant:** The default art or guest photo set currently assigned to a replaceable symbol.
- **Replacement:** The atomic transition in which a new winner takes over one symbol and its previous occupant is archived.
- **Event history:** An immutable sequence of wins, uploads, replacements, arrivals, departures, and protected matches.
- **Spin authorization:** A server-created result token defining the final outcome of a spin.
- **Celebration rate:** The configured probability that an ordinary spin becomes a winning match.

## 4. Supported Clients

### 4.1 PWA
- iPhone Safari and installed Home Screen mode.
- Android Chrome and installed PWA mode.
- Desktop Chrome, Edge, Safari, and Firefox where supported.
- Responsive portrait-first interface with landscape and desktop layouts.

### 4.2 Chrome Extension
- Manifest V3 extension.
- Uses the same compiled UI and game modules as the PWA.
- Extension-specific adapter handles storage, notifications, launch behavior, and optional AirBridge extension messaging.
- No remotely hosted executable JavaScript.

### 4.3 Backend
Initial target:
- Linux/Apache/PHP 8.3+.
- MySQL 8+ or MariaDB 10.6+.
- Redis recommended for presence, locks, rate limits, queues, and real-time fan-out.
- WebSocket service may be implemented in PHP with Workerman/Ratchet, or as a small Node.js gateway. REST remains PHP.
- Object storage can begin as protected local filesystem storage and later move to S3-compatible storage.

## 5. Wedding Configuration

The admin console must support:

- Wedding title.
- Couple names.
- Event date and local timezone.
- Public join slug.
- Short join token.
- Launch icon image.
- Splash/hero image.
- Application background image.
- Three protected couple images.
- Seventeen default replaceable wedding-themed symbol identities.
- Optional per-symbol label.
- Background music.
- Spin sound.
- Normal win sound.
- Made in Heaven long failure/comedy tone.
- Guest-arrival announcement sound.
- Guest-departure announcement sound.
- Announcement display duration.
- Upload timeout.
- Replacement pause behavior.
- Celebration rate.
- Minimum delay between spins per guest.
- Maximum simultaneous players, where zero means no application-level limit.
- Guest-name policy: required, optional, or generated.
- Moderation mode: immediate, winner-preview, or administrator-approval.
- Whether removed guests receive a private notification.
- Whether names are displayed publicly.
- Whether final gallery is private or shareable.
- Event open and close times.
- Archive retention policy.

## 6. Reel and Symbol Model

### 6.1 Logical identities
There are exactly 20 logical symbol identities:

- IDs 1-3: protected couple images.
- IDs 4-20: 17 replaceable symbols.

The protected images are visually different but belong to one collective match class named `MADE_IN_HEAVEN`.

Each replaceable symbol identity has an occupant record containing:
- Occupant type: default or guest.
- Guest ID when occupied by a guest.
- One to three image assets.
- Display name.
- Start time.
- Replacement version.
- Previous occupant reference.

### 6.2 Forty stops per reel
Each of the three reels contains 40 visual stops. The initial default strip contains two occurrences of each of the 20 logical identities. Strip order should be separately shuffled for each reel while avoiding obvious adjacent duplicates where possible.

The physical strip controls animation and visual continuity. It does **not** alone determine the outcome probability. The server outcome engine selects the final logical identities, then maps them to matching stop indices on each reel.

This separation is required because natural three-reel matching with 20 equally distributed symbols occurs only once in 400 spins. That is too rare for the social photo-history goal.

### 6.3 Configurable celebration rate
Default configuration:
- `win_rate = 0.05` (approximately one normal win per 20 completed spins).
- Admin range: 0.02 to 0.10 (approximately one per 50 to one per 10).
- Recommended production range: 0.03 to 0.07.

On each authorized ordinary spin:
1. The server uses a cryptographically secure random draw to determine win versus non-win.
2. For a normal win, the server uniformly selects one of the 17 replaceable symbol identities unless a different eligible policy is explicitly configured.
3. For a Made in Heaven event, the server uses a separate configurable rate, default `0.005` (approximately one per 200 spins). This event does not replace anything.
4. A non-winning result is generated so the three match classes are not all equal.
5. Every server decision is logged with a commitment/hash for auditability.

Equal odds means every replaceable symbol occupant has the same chance to be the target **when a normal win occurs**. The overall rate of winning is independently configurable.

### 6.4 Protected match semantics
Any outcome in which all three visible reel images are members of the protected set counts as Made in Heaven. Examples:
- Couple image 1 / image 1 / image 1.
- Image 1 / image 2 / image 3.
- Image 3 / image 2 / image 3.

The response:
- No camera entitlement.
- No replacement.
- Long comic failure tone.
- On-screen message: “This match was made in heaven and cannot be replaced.”
- Optional couple animation/confetti/hearts.
- Immutable history event.

## 7. Guest Experience

### 7.1 Join
Guests can join through:
- QR code containing an HTTPS join URL.
- Typed short URL.
- Shared link.
- AirBridge payload containing a compact wedding ID and short-lived join token.
- Chrome extension launch page.

Join sequence:
1. Resolve token.
2. Fetch wedding theme and current symbol state.
3. Ask for display name according to policy.
4. Create or restore guest session.
5. Ask for notification permission only after explaining its benefit.
6. Enter the live lobby/game.

No microphone permission is required unless the device is actively listening for AirBridge.

### 7.2 Spin
1. Guest presses Spin.
2. Client sends `POST /spins`.
3. Server validates event state, session, rate limit, and current game version.
4. Server returns a signed spin authorization with final symbol IDs, target stop indices, duration, and result type.
5. Client animates without changing the result.
6. Client acknowledges completion.
7. Server emits any result event.

The client must never calculate an authoritative win.

### 7.3 Normal win
1. All three reels stop on the same replaceable symbol identity.
2. Winner sees a celebration and a capture entitlement.
3. Other clients see “A winner is taking over a reel symbol.”
4. New spins are handled according to pause policy.
5. Winner captures one to three images.
6. Winner previews, retakes, and submits.
7. Server processes and validates images.
8. Replacement commits atomically.
9. All clients receive the arrival and departure announcement.
10. Reels update to the new occupant.
11. Play resumes.

### 7.4 Arrival and departure announcement
Every committed replacement must identify both sides.

Public example:
> Alex is now on the reels! Jamie has been kicked off - but Jamie can keep spinning to win a place back.

The announcement must include:
- New guest display name and photos.
- Removed occupant display name and photos when the removed occupant was a guest.
- A gentler message when default wedding art is removed.
- Symbol identity/label.
- Timestamp.
- Optional animation of old images leaving and new images entering.

The removed guest's active sessions receive:
> You have been kicked off the reels. Keep spinning to get back on!

Removed images are archived, never destroyed by the replacement operation.

### 7.5 Upload abandonment
If the winner does not submit valid photos before the entitlement expires:
- No replacement occurs.
- The original occupant remains.
- The reservation unlocks.
- A timeout event is recorded.
- Play resumes.
- The winner may receive an explanatory message.

Default entitlement duration: 120 seconds, admin-configurable from 30 to 300 seconds.

## 8. Global Pause and Concurrency

Supported pause policies:
- `finish_in_progress`: Recommended. Existing spins finish; new spins are disabled during capture and commit.
- `pause_on_commit`: Spins continue during capture, but new spins pause briefly while the replacement is broadcast.
- `no_global_pause`: Replacements use versioning without pausing; suitable only after stress testing.

Initial release should use `finish_in_progress`.

Wedding game states:
- `OPEN`
- `REPLACEMENT_PENDING`
- `COMMITTING_REPLACEMENT`
- `ANNOUNCING`
- `PAUSED_BY_ADMIN`
- `CLOSED`

Only one replacement may commit at a time per wedding. Use a database transaction plus a distributed lock. Competing wins that occur before the pause becomes visible are queued in server order. A queued winner's target symbol is revalidated before capture; the simplest Version 1 policy is to allow only one outstanding photo entitlement and convert later overlapping wins into celebratory non-replacement wins.

## 9. Image Capture and Processing

### 9.1 Capture
- Use `getUserMedia` where supported.
- Fall back to `<input type="file" accept="image/*" capture="user">`.
- Permit one, two, or three images.
- Support retake and reorder.
- Never upload until the guest explicitly submits.

### 9.2 Client preprocessing
- Correct EXIF orientation.
- Center-crop a square preview.
- Maximum source dimension before upload: 2048 pixels.
- Strip unneeded metadata.
- Prefer WebP quality 0.82; JPEG fallback.
- Show upload progress.

### 9.3 Server processing
Create:
- Original retained only when configured.
- 1024x1024 archive image.
- 512x512 reel image.
- 160x160 thumbnail.
- WebP primary and JPEG fallback as needed.
- SHA-256 hash.
- MIME validation by content, not filename.
- Decode/re-encode to neutralize malformed metadata.
- Optional moderation queue.

### 9.4 Content safeguards
Admin controls:
- Remove an image immediately.
- Restore prior occupant.
- Ban a session.
- Hide a name.
- Freeze replacements.
- Close the event.

The app should display a brief consent notice before upload explaining that submitted photos will be shown to wedding guests and archived for the couple.

## 10. Event Photo History

The backend must preserve:
- Every occupant version.
- Every image asset version.
- Every normal win.
- Every Made in Heaven match.
- Every capture entitlement.
- Every upload and moderation decision.
- Every arrival announcement.
- Every departure announcement.
- Which guest replaced whom.
- Time spent on the reel.
- Re-entry count for each guest.
- Final live state.

At event close:
1. Reject new spins.
2. Finish or cancel current replacement according to admin choice.
3. Snapshot all 20 logical identities and 40-stop reel mappings.
4. Generate a chronological history.
5. Generate an optional downloadable gallery package.
6. Preserve the three protected symbols permanently.
7. Mark the snapshot immutable.

## 11. AirBridge Integration

AirBridge is a discovery channel, not the application data transport.

Recommended payload:
- Protocol version.
- Message type `WEDDING_JOIN`.
- Compact wedding public ID.
- Short-lived join token or nonce.
- Optional server/environment code.
- Integrity check supplied by the AirBridge protocol.

After detection:
1. AirBridge adapter invokes a callback.
2. Client validates payload type and freshness.
3. Client constructs the normal HTTPS join URL.
4. User confirms opening/joining unless platform policy permits a direct in-app transition.
5. All theme, game, images, and live state load over HTTPS.

Never place private guest data, permanent credentials, or image content in the acoustic payload.

Adapters:
- `airbridge-browser-adapter.js`
- `airbridge-extension-adapter.js`
- no-op adapter for unsupported clients

The core app consumes a normalized event:
```js
window.dispatchEvent(new CustomEvent("airbridge:join", {
  detail: { weddingPublicId, joinToken, receivedAt }
}));
```

## 12. Architecture

### 12.1 Frontend modules
- `app-shell`
- `platform-adapter`
- `auth-session`
- `wedding-bootstrap`
- `reel-engine`
- `spin-controller`
- `realtime-client`
- `camera-capture`
- `image-uploader`
- `announcement-overlay`
- `history-viewer`
- `admin-console`
- `airbridge-adapter`
- `service-worker`
- `extension-wrapper`

Use TypeScript and a component framework only if desired. A lightweight Vite + TypeScript implementation is preferred. React, Vue, or Lit are acceptable; do not mix frameworks.

### 12.2 Backend modules
- Authentication/session service.
- Wedding configuration service.
- Spin authorization service.
- Replacement workflow service.
- Image service.
- Real-time event publisher.
- History/audit service.
- Admin service.
- Scheduled cleanup/archive jobs.

### 12.3 Recommended repository
```text
wedding-reels/
  apps/
    web/
    extension/
    admin/
  packages/
    core/
    ui/
    platform/
    protocol/
  server/
    public/
    src/
      Controllers/
      Services/
      Domain/
      Repositories/
      Realtime/
      Jobs/
    database/
      migrations/
      seeds/
  infra/
    apache/
    docker/
  tests/
    unit/
    integration/
    e2e/
    load/
  docs/
```

### 12.4 Real-time transport
Preferred:
- WebSocket for announcements, state changes, presence, and pause/resume.
- REST for commands, uploads, bootstrap, and history.
- Server-Sent Events fallback is acceptable.
- Polling fallback every 3-5 seconds for restricted environments.

Every event includes:
- `eventId`
- `weddingId`
- `sequence`
- `stateVersion`
- `type`
- `occurredAt`
- typed payload

Clients discard duplicate events and request a state refresh after a sequence gap.

## 13. Data Model

Principal tables:
- `weddings`
- `wedding_assets`
- `guests`
- `guest_sessions`
- `symbol_identities`
- `symbol_occupants`
- `occupant_images`
- `reel_strips`
- `spins`
- `capture_entitlements`
- `replacement_events`
- `announcements`
- `event_log`
- `final_snapshots`
- `admin_users`
- `moderation_actions`

Important rules:
- Protected identities cannot be updated by guest workflows.
- One current occupant per replaceable symbol.
- Replacement writes old occupant end time and new occupant start time in one transaction.
- Event sequence increments monotonically per wedding.
- Asset rows are immutable; replacement changes references rather than overwriting files.

## 14. API Summary

Public/bootstrap:
- `POST /api/v1/join/resolve`
- `POST /api/v1/sessions`
- `GET /api/v1/weddings/{publicId}/bootstrap`
- `GET /api/v1/weddings/{publicId}/state`
- `GET /api/v1/weddings/{publicId}/history`

Game:
- `POST /api/v1/weddings/{publicId}/spins`
- `POST /api/v1/spins/{spinId}/complete`
- `POST /api/v1/spins/{spinId}/claim`
- `POST /api/v1/entitlements/{id}/images`
- `POST /api/v1/entitlements/{id}/commit`
- `POST /api/v1/entitlements/{id}/cancel`

Admin:
- CRUD wedding configuration.
- Upload branding/assets.
- Open, pause, resume, and close wedding.
- Moderate assets.
- Restore occupant.
- Export history.
- View live metrics.

## 15. Spin Authorization Contract

Example:
```json
{
  "spinId": "spn_01J...",
  "stateVersion": 184,
  "resultType": "NORMAL_WIN",
  "symbols": [12, 12, 12],
  "targetStops": [7, 25, 3],
  "durationMs": 2600,
  "easingProfile": "wedding-bounce-v1",
  "claimToken": "signed-short-lived-token",
  "serverCommitment": "sha256...",
  "expiresAt": "2026-07-15T05:11:20Z"
}
```

Result types:
- `NO_WIN`
- `NORMAL_WIN`
- `MADE_IN_HEAVEN`
- `CELEBRATION_ONLY`
- `EVENT_PAUSED`
- `EVENT_CLOSED`

## 16. Replacement Event Contract

```json
{
  "eventId": "evt_01J...",
  "type": "SYMBOL_REPLACED",
  "sequence": 901,
  "stateVersion": 185,
  "symbolId": 12,
  "symbolLabel": "Wedding Bells",
  "addedOccupant": {
    "guestId": "gst_...",
    "displayName": "Alex",
    "images": [
      {"url": "/media/...", "thumbnailUrl": "/media/..."}
    ]
  },
  "removedOccupant": {
    "type": "GUEST",
    "guestId": "gst_...",
    "displayName": "Jamie",
    "images": [
      {"url": "/media/...", "thumbnailUrl": "/media/..."}
    ]
  },
  "publicMessage": "Alex is now on the reels! Jamie has been kicked off - keep spinning to get back on.",
  "occurredAt": "2026-07-15T05:10:00Z"
}
```

## 17. Security

- HTTPS only.
- Secure, HttpOnly, SameSite cookies for browser sessions where practical.
- CSRF protection for cookie-authenticated commands.
- Short-lived signed join and claim tokens.
- Never trust client-declared wins, symbol IDs, or replacement targets.
- Per-session and per-IP rate limits.
- File size, pixel count, MIME, and decode validation.
- Image re-encoding.
- Randomized non-guessable asset paths or authenticated media gateway.
- Admin MFA strongly recommended.
- Separate admin and guest authorization policies.
- Audit all privileged actions.
- Content Security Policy compatible with PWA and MV3.
- No remote executable extension code.
- Database transactions and idempotency keys for replacement commits.
- Optimistic state version plus distributed lock.
- Privacy-aware logs: do not log raw images or join secrets.
- Configurable deletion/export process.

## 18. Accessibility and UX

- Minimum 44x44 CSS pixel touch targets.
- Keyboard-operable desktop/extension controls.
- Reduced-motion mode.
- Captions/text equivalents for announcements and sounds.
- High-contrast readable overlays.
- Alt text generated from occupant names and symbol labels.
- Do not rely solely on red/green.
- Screen-reader live regions for arrivals, departures, pause, and win status.
- Camera and notification permission requests must include explanations.

## 19. Offline and Failure Behavior

The game requires server authorization for spins and therefore must not create replacement-eligible offline wins.

Offline capabilities:
- App shell and wedding theme cache.
- Display last known reels with an offline banner.
- Browse cached history.
- Queue only harmless analytics.
- Disable Spin while disconnected.
- Retry resumable image upload while entitlement remains valid.
- On reconnection, fetch current state before enabling Spin.

Failure rules:
- WebSocket disconnect: switch to polling and show status.
- Upload failure: preserve preview locally and retry.
- Commit timeout: idempotently query entitlement state.
- Sequence gap: full state refresh.
- Replacement conflict: server returns canonical state.
- Asset failure: show wedding-themed placeholder.

## 20. Admin Console

Pages:
1. Sign in.
2. Wedding setup wizard.
3. Theme and assets.
4. Reel symbol manager.
5. Gameplay pacing.
6. Sound and announcement editor.
7. Live event dashboard.
8. Moderation queue.
9. Guest/session management.
10. History timeline.
11. Final snapshot and exports.
12. Audit log.

Live dashboard metrics:
- Connected guests.
- Spins per minute.
- Wins.
- Made in Heaven matches.
- Pending capture.
- Replacement count.
- Current occupants.
- Most re-entries.
- Upload errors.
- Real-time connection health.

## 21. Testing

### Unit
- Equal target selection across 17 replaceable symbols.
- Configured win-rate boundaries.
- Non-win generator never creates an accidental match class.
- Protected class combinations.
- Reel stop mapping.
- State machine transitions.
- Announcement wording.
- Image validation.
- Token expiration.
- Idempotency.

### Integration
- Join/bootstrap.
- Authorized spin.
- Claim and upload.
- Atomic replacement.
- Arrival/departure broadcast.
- Removed guest private notice.
- Concurrent claims.
- Admin pause/close.
- Snapshot generation.

### End-to-end
- iPhone Safari.
- Android Chrome.
- Installed PWA.
- Desktop browser.
- Loaded unpacked MV3 extension.
- Camera permission denied.
- Notification permission denied.
- WebSocket fallback.
- AirBridge join callback.
- Guest wins, joins reel, is removed, and wins back on.

### Load
Initial acceptance:
- 500 concurrent connected clients per wedding.
- 50 spin requests/second burst.
- One replacement commit while all clients receive ordered update within 2 seconds at p95.
- No duplicate replacement after retries.
- Reconnect storm recovery.

## 22. Acceptance Criteria

The first production release is accepted when:

1. Admin can create a wedding and upload all required images and sounds.
2. A QR link opens the correct themed game.
3. AirBridge adapter can resolve a valid compact join payload.
4. Three reels display 40 stops and 20 identities.
5. Server controls every outcome.
6. Celebration rate is configurable and statistically verified.
7. Every replaceable identity is equally selectable on wins.
8. All protected-image combinations trigger Made in Heaven and cannot open camera capture.
9. A normal winner can submit one to three photos.
10. The photos replace the matched identity across all clients.
11. The added and removed guests are publicly announced.
12. The removed guest receives a private encouragement message when enabled.
13. Removed media remains in history.
14. A removed guest can later win back onto any replaceable identity.
15. Concurrent replacement attempts cannot corrupt state.
16. Admin can pause, moderate, restore, close, and export.
17. Event close produces an immutable final snapshot and chronological gallery.
18. PWA and extension use the same core code.
19. Core flows pass on iPhone, Android, desktop Chrome, and MV3 extension.
20. No wager, payment, or cash-prize functionality exists.

## 23. Delivery Phases

### Phase 0 - Repository and contracts
- Monorepo.
- Coding standards.
- Local Docker environment.
- Shared domain types.
- API/error/event conventions.
- CI.

### Phase 1 - Wedding setup and static reels
- Admin authentication.
- Wedding CRUD.
- Asset upload.
- Bootstrap endpoint.
- Responsive three-reel UI.
- PWA manifest and service worker.
- Extension wrapper.

### Phase 2 - Authoritative spins
- Spin engine.
- Forty-stop mapping.
- Configurable celebration rate.
- Protected matches.
- Signed authorizations.
- Unit/statistical tests.

### Phase 3 - Real-time operation
- WebSocket gateway.
- Sequence/version protocol.
- Presence.
- Pause state.
- Ordered announcements.
- Fallback polling.

### Phase 4 - Camera and replacement
- Capture workflow.
- Processing pipeline.
- Entitlements.
- Atomic replacement.
- Public arrival/departure.
- Private removed-guest notice.

### Phase 5 - History and administration
- Timeline.
- Moderation.
- Restore.
- Metrics.
- Event close.
- Final snapshot/export.

### Phase 6 - AirBridge
- Normalized adapter.
- Extension messaging.
- Join token validation.
- User-confirmed launch.
- Integration tests with mocked modem callback.

### Phase 7 - Hardening
- Security review.
- Accessibility review.
- Load testing.
- Browser/device matrix.
- Deployment and backup procedures.
- Production observability.

## 24. Explicit Non-Goals for Version 1

- Real-money gambling.
- Prizes with monetary value.
- Native iOS or Android application.
- Face recognition.
- Automatic identification of photographed guests.
- Direct image transport through AirBridge.
- Offline replacement-eligible spins.
- Public social network outside the wedding.
- Multiple simultaneous replacement captures for one wedding.
