Felt Faction iOS app v5 — Felty as the Bank + working hand semantics

Writing Search related

Brainstormed 2026-06-16. User resolved most remaining design questions.

The big synthesis: Felty is the Bank character. The existing

Telegram bot is now the iOS app's house voice. Connor is Felty's

human proxy. This unifies the system's personality.

Decisions (locked in by user, this round)

  1. Bank interest rate = steep. Real casino marker (10-20% per

session). The bank feels like a real house edge.

  1. Working hand persists across the whole night. Members have

the same chip stack for both Phase I and Phase II. To add funds mid-night, members "take out more from their net worth / pool." (See clarifying questions — "net worth" definition needs final wording.)

  1. Season-starting chip allocation. At the start of each season

(or phase — TBD), each member gets X amount of chips. X is configurable. (See clarifying questions — what is "phase" here?)

  1. Settings tab for chip denominations. Chip set (denominations

+ colors) is registered via a settings tab, not a one-time setup flow. Saved as presets. Recalled per session.

  1. Season Awards locked: Phoenix, Veteran, Whale, Drowning.

Four awards confirmed. User said "these all seem good" — may include more; these four are the floor.

  1. Felty is the Bank character. "Connor is like felty's human

puppet." The Telegram bot's existing personality is now the in-app house voice. Felty issues loans, debits interest, sends no-show tax notifications, narrates the live settlement board, etc.

  1. "$X left until Y breaks even" — the live redemption-arc

indicator. Members see how much more they need to win to pay off their loan and earn the Phoenix. Tentative agreement, not fully locked.

The big synthesis: Felty is the Bank

The existing Telegram bot is Felty. Per notes.md:

"Felty: Telegram bot agent (separate from TARS)"

"Workspace: ~/.openclaw/workspaces/felty/"

Now Felty is the personality of the entire system:

SurfaceFelty's voice
Push notifications"Felty: the Briefing is in. Saturday 7pm."
Loan offer"Felty: the House extends credit. 500 chips at 15%."
Interest debit"Felty: the rake takes its cut. −75 chips."
No-show tax"Felty: you didn't show. The House remembers. −200 CC."
Live settlement board"Felty: Olivia: +$237. Chan: +$89. Aston: pending..."
Phoenix badge"Felty: the comeback is complete. The Phoenix rises."
Event close"Felty: the Veteran was Aston last night. Tonight it returns to Lucy. The Whale surfaces. The Drowning holds."

Connor is "Felty's human puppet" — the on-the-floor Pit Boss, but the system speaks with Felty's voice. Connor resolves disputes on behalf of Felty. The human and the system are a unit, but the system has the consistent personality.

Implications

  1. Felty must have a defined voice. Finance-noir, sharp, witty,

not cartoonish. Felty is the house voice in a Monte Carlo sense, not a Vegas cartoon. "The House" terminology reinforces this.

  1. **Felty's Telegram bot and the iOS app Felty are the same

character.** Same voice, same rules, same authority. The platform changes; the personality doesn't.

  1. Felty can be queried. Members can ask Felty things in plain

language: "What's my standing?" "Who has the most debt?" "When is the next session?" Felty answers.

  1. Felty has moods / state. When you have a loan, Felty's

voice gets a touch more formal. When you're close to breaking even, Felty might say "the comeback is close." When the Phoenix is earned, the voice shifts to "the House celebrates your return."

  1. **Felty's messaging is the House's communication.** Connor

speaks as Connor; Felty speaks as the system. Two voices, two authorities, one team.

The working hand / net worth mechanic

User clarified:

"you have the same working hand/chips for the whole night, unless

you take out more from your networth/pool"

This is a new mechanic that wasn't in v4. The economy now has two buckets:

BucketWhat it isLifecycle
Working handThe active chip stack at the tablePersists all night (across both phases)
Net worthThe persistent season CC balanceGrows / shrinks based on P&L; can be drawn down to refill the working hand

The "net worth" is a source of liquidity, not a separate game-state. When your working hand runs low, you "buy in" from your net worth. The "buy in" doesn't reset the working hand; it augments it. Your net worth decreases by the buy-in amount.

This is the casino front-money model:

(or you walk with chips if you want)

The bank's loan mechanic still applies at zero working hand — when you can't even refill from your own net worth, the House extends credit. The loan is the "marker" the casino extends when your front money runs out.

Open: "net worth" definition

"Net worth" could mean:

user might be introducing a new concept)

Lean toward (a) — the season CC balance is the natural fit. The member's season-long ledger is what grows/shrinks based on P&L; they can draw from it to refill the table stack. TBD with user.

The "$X left until Y breaks even" indicator

User's idea: on the live settlement board, for each member with an outstanding loan, show how much more they need to win to pay it off and earn the Phoenix. The redemption arc is quantified and live.

Example: "Olivia: +$237. Loan: $400 + 15% interest = $460. $223 left until Phoenix."

This is a powerful narrative device. The room sees who's close to redemption. The Phoenix arc is not hidden — it's the headline of the settlement board.

Possible surfaces:

tentative

away" (could be motivating or depressing; UX judgment call)

User said "this could be interesting" — leaning toward yes, but not locked. Design the v1 surface as the live settlement board only; revisit Watch / in-app for v2.

Settings tab for chip denominations

The user wants a settings tab where chip denominations and colors are configured. Saved as presets. Recalled per session.

This is the chip-set registration surface. Not a one-time setup flow. Connor (or whoever) can:

with a name)

The session-start flow becomes:

  1. Connor hits "Start session"
  2. Connor selects the chip set for tonight (or "use default")
  3. Members self-buy-in
  4. Play

Season Awards: four locked

User confirmed:

the Veteran Award can only be awarded to a user that is not an admin — the Veteran's meaning is "regular attendee reliability," which doesn't apply to admins who have different obligations)*

The Shark, Catalyst, Most Improved, Consummate Host were not rejected — the user just confirmed these four. The Shark and Catalyst can stay as in-session personas (per the v3 intel plan), but the season-end awards are these four.

The Shark and Catalyst are in-session signals; the Phoenix, Veteran, Whale, Drowning are season-long signals. Different cadences. The season finale surfaces the season awards; the Vault recap surfaces the in-session personas.

Veteran Award qualification rule (2026-06-20 refinement)

The Veteran Award can only be awarded to a user that is not an admin. This is a per-award qualification rule — the other three awards (Phoenix, Whale, Drowning) remain open to admins. The Veteran specifically measures "regular attendee reliability," which doesn't apply to admins who have different obligations (host duty, Felty's proxy role, etc.).

Enforcement (LOCKED 2026-06-20, commit fe724f2): DB-level BEFORE INSERT OR UPDATE trigger on season_awards. The user's initial framing was "SQL CHECK constraint" (option 1 of three enforcement layers I surfaced) — but PostgreSQL CHECK constraints can't have subqueries (ERROR: 0A000: cannot use subquery in check constraint). The closest DB-level analog that PostgreSQL actually supports is a BEFORE INSERT OR UPDATE trigger, which CAN query other tables and fires regardless of role (RLS WITH CHECK would be bypassed by the service role — Felt Faction's scheduled awards at season end run as service role).

CREATE OR REPLACE FUNCTION public.enforce_veteran_award_non_admin()
RETURNS TRIGGER AS $$
BEGIN
  IF NEW.award_type = 'veteran' THEN
    IF EXISTS (
      SELECT 1 FROM public.members
      WHERE id = NEW.member_id AND is_admin = true
    ) THEN
      RAISE EXCEPTION 'Veteran Award can only be awarded to a non-admin member (recipient % is admin)', NEW.member_id
        USING ERRCODE = 'check_violation';
    END IF;
  END IF;
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER veteran_award_non_admin_trigger
  BEFORE INSERT OR UPDATE ON public.season_awards
  FOR EACH ROW
  EXECUTE FUNCTION public.enforce_veteran_award_non_admin();

Verified end-to-end (negative test): ERROR 23514: Veteran Award can only be awarded to a non-admin member (recipient cf86c6fa-... is admin).

The trigger protects against the AUTO-ASSIGNMENT logic accidentally giving Veteran to an admin (see next section) — the constraint fires for every INSERT regardless of who/what inserts (admin session, server-side cron with service role, app's scheduled job).

Auto-assignment of Season Awards (2026-06-20 refinement)

User's exact phrasing:

"the award aren't assigned by the admins, they are

automatically assigned, by the app"

Season Awards are auto-assigned by the app, not manually assigned by admins. This is a meaningful refinement to the admin workflow:

(admin hits "Finalize Season" → awards are computed and INSERTed via the admin's session).

season finale automatically. Admins don't pick winners; the system does. Admins might still trigger the finale (a "Close Season" button), but the award computation + INSERT happens via the app, not via admin selection.

Implication: the DB-level trigger (above) is the right enforcement layer. Auto-assigned awards mean the constraint isn't about "preventing admins from picking each other" — it's about "the auto-assignment logic must not violate the rule even by accident." A bug in the award computation code (e.g., a query that forgets to filter is_admin = false for the Veteran) would produce a wrong INSERT. The DB trigger catches the bug at write time. Defense-in-depth.

Felty's voice for the finale (locked): when the auto-assignment runs, Felty narrates each award:

road is long, the path was walked."

The deep pockets hold."

The comeback is complete."

The House remembers."

Mechanism (TBD — open design questions):

  1. Trigger for the auto-assignment — when does it run?

app runs the computation, the finale is a moment the admin + members witness together). Per the real-casino framing, the finale is a ritual — admins initiate, app narrates, members watch the badges appear.

  1. Credentials for the auto-assignment INSERT — admin's

session or service role? The DB trigger fires either way, but

(admin sees the same row they'd see in the UI)

server-side cron

admin UI; the admin's iOS session writes the rows; RLS policies apply naturally; the admin sees what they wrote).

  1. Idempotency — what happens if the admin re-runs the

finale?

safety net: a second run can't create duplicate award rows for the same (season, type).

run). Currently the trigger fires on UPDATE too — this could break re-runs if the values change (e.g., the computation re-runs and the "correct" Whale changes from Aston to Olivia because Aston withdrew). Open: should the trigger allow UPDATE if the award_type doesn't change? Currently it re-checks the rule on every UPDATE, which is correct (a member who becomes admin later can't have a Veteran award row updated to "still be valid for an admin" — UPDATE fails).

  1. Felty's push notifications — when does the iOS app

notify members of the awards?

surface. The auto-assignment fires Felty-voiced pushes to every member: "Felty: season close. Felty: The Veteran is you, Olivia. Eleven sessions walked."

Veteran Award qualification rule — updated enforcement rationale

With the auto-assignment refinement, the DB-level trigger is even more important. The auto-assignment logic is a SQL query that returns the Veteran based on "most sessions attended." A bug in that query (e.g., forgetting to exclude admins) would attempt to insert a Veteran for an admin. The trigger catches the bug at write time.

This is a defense-in-depth pattern:

filtering WHERE members.is_admin = false. The app code is correct.

inserts. If the app code has a bug, the trigger catches it.

If the app code is correct AND the trigger passes, the Veteran goes to a non-admin. The constraint is held by both layers.

Award cadence: per-event close, badges may change hands (2026-06-20 refinement)

User's exact phrasing:

"at the close of each event/session the badges will get updated

and maybe change hands"

Awards are updated at the close of EACH event/session, not just at the close of the season. This is a meaningful refinement to the cadence:

season (at the season finale). One Veteran, one Whale, one Phoenix, one Drowning per season. The badges were stable throughout the season.

event close. The badges are dynamic — they recompute and may change hands. If Aston had the Veteran last event and Lucy attended this event, the Veteran returns to Lucy.

Implications for each award:

AwardCadence"Changes hands" semantics
The VeteranPer eventActive. "Most sessions attended" recomputes — high attendance over a few events can shift this.
The WhalePer eventVery active. "Highest CC" shifts with every event's P&L. The Whale today may be a different member than the Whale tomorrow.
The PhoenixOnce-per-arcStable once earned. The Phoenix is "paid back a loan with interest" — once earned, it's yours. Doesn't change hands. (Open: if a member earns Phoenix then takes a new loan, does Phoenix disappear? Lean: Phoenix stays — you don't un-pay the House. The new loan is a fresh arc, separate from the prior redemption.)
The DrowningPer eventVery active. "Most debt" shifts as members pay down or take on new debt.

The "change hands" detail makes the awards dynamic narrative beats rather than static trophies. Members watch the badges shift event to event. Felty narrates each transition:

to Lucy. Three events walked, none missed."

Z dives — the Drowning holds."

Schema implication (open question): the season_awards table's UNIQUE (season_id, award_type) constraint was designed for one-award-per-type-per-season. With per-event updates, the uniqueness key needs to change. Options:

per-event award log. Each event close INSERTs a new row. The current award = the most recent row per (member, award_type).

award_type)`. One row per event per award type. Same as (a) in effect, but the constraint enforces it at write time.

in a separate current_awards table** (denormalized), with season_awards as the historical log.

Lean: (b)UNIQUE (event_id, award_type). Maintains the constraint-at-write-time pattern (defense-in-depth, like the Veteran trigger), avoids a separate "current holder" table, the "current award" is just WHERE event_id = (SELECT MAX(event_id) FROM events WHERE status = 'closed'). The DB schema change is a migration (drop old UNIQUE, add new one) — non-destructive besides the constraint shape.

The DB-level Veteran trigger (fe724f2) is still correct. The trigger is per-row, independent of the UNIQUE shape. It validates the rule on every INSERT regardless of how many rows exist per (event, award_type) pair. No trigger change needed.

Felty's per-event close voice (NEW, locked):

remembers."

(depending on whether the holder changed)

dives." (depending on CC delta)

comeback is complete." (only fires when a new Phoenix is earned)

new Drowning, or no change)

v1 cut (final)

Carried:

  1. Glance (Widget + Live Activity + Watch complication)
  2. Claim Your Seat from Lock Screen
  3. Apple Watch app
  4. Quick Settle (Face ID, Taptic, big numbers)
  5. Native push for Briefing
  6. Buy-in flow (with + for rebuy)
  7. Cashout with chip photo (per-member individual)
  8. Self-attest / audit (admin override)
  9. Push notification lifecycle

New in v1:

  1. Felty's voice (the system personality across all surfaces)
  2. The Bank (system role, loan at zero, interest, auto-debit)
  3. Live settlement board (Felty-narrated, debt column with

"X left until Y breaks even")

  1. Settings tab for chip denominations (saved presets)
  2. Per-event award cadence (auto-assignment at every event

close; badges may change hands; UNIQUE on (event_id, award_type) instead of (season_id, award_type))

Season Awards → v2 (only surfaces at season end) → updated to v2: awards surface at every event close. The v2 timeline shifted from "once per season" to "per event."

Outstanding clarifying questions

  1. "Net worth" definition — is it the season CC balance, a

separate front-money pool, or all-time CC? Lean: season CC.

  1. "Season/phase" in the season-starting chip allocation — at

the start of each season (3-month cycle), each member gets X chips? Or at the start of each session/phase within a season? Different economies. Lean: at the start of each season.

  1. "X left until Y breaks even" — confirm this is on the live

settlement board in v1, defer Watch / in-app to v2? Lean: yes.

  1. Veteran Award non-admin enforcement (LOCKED 2026-06-20) —

DB-level trigger (fe724f2). Mechanism: BEFORE INSERT OR UPDATE trigger on season_awards.

  1. Auto-assignment trigger (2026-06-20, open) — Option C

(admin "Close Season" button → app computes + inserts via admin's session). Idempotent on UNIQUE (season_id, award_type). Felty-voiced push notifications to all members.

  1. Auto-assignment schedule (open) — when is "season end"?

Calendar-based (3-month cycle)? Admin-declared? Per the v5 spec, seasons are 3-month cycles; the "Close Season" button is the manual trigger; calendar reminders nudge the admin.

  1. Per-event award cadence (LOCKED 2026-06-20) — auto-assignment

at every event close. Badges may change hands. Schema change pending: UNIQUE on (event_id, award_type). Migration TBD.

  1. Phoenix "earned once" semantics (open) — does Phoenix stay

if the member takes a new loan after earning it? Lean: yes, stays (the prior redemption is a historical fact; the new loan is a separate arc). Open question.

What the design looks like end-to-end (v5)

A Felt Faction night, under v5 design:

  1. Pre-session (48h): Felty pushes: "The Briefing is in. Saturday

7pm. 9/12 confirmed." Members claim seats.

  1. Session start: Connor hits "Start session." Felty pushes

the no-show tax: "Connor, Aston didn't show. −200 CC applied." Members self-buy-in. Working hand issued.

  1. Phase I (Casino): Play. Members can rebuy ("add funds from

net worth"). At zero, Felty offers the loan: "The House extends credit. 500 chips at 15% interest. Take it?"

  1. Phase II (Social): Same working hand. CAH / Muffin Time —

chips awarded. Felty narrates: "Olivia: +5 chips. Chan: +3."

  1. Settle: Felty pushes: "Settle now. Photo your stack." All

12 take photos. Live settlement board resolves (Connor's screen). Felty narrates: "Olivia: +$237. Loan: $460. $223 left until Phoenix. Chan: +$89..." Self-attest. Connor finalizes. Photos auto-delete. Felty: "Settled."

  1. Post-session / Event close (NEW, per-event cadence):

App auto-computes the awards (Veteran, Whale, Drowning — and Phoenix if newly earned this event). App INSERTs rows into season_awards with event_id = <this event>. UNIQUE on (event_id, award_type) enforces one-row-per-event-per-type. The Veteran Award trigger fires and rejects any attempt to award Veteran to an admin. Felty narrates each award to the iOS Standings tab + pushes notifications to every member. "Felty: the Veteran rises. Lucy, three events walked." "Felty: the Whale surfaces. Aston, 4,200 CC at the surface." "Felty: the Drowning holds. Chan, 800 CC below water."

The bank is the system. Felty is the system's voice. The Phoenix is the redemption. The "Don't Fold, Finance" tagline is literal. The badges are dynamic — they update and may change hands event to event.

See also

Superseded by v5 for the Felty-as-Bank character and the working hand / net worth mechanic; v4's technical primitives (Bank as system role, Phoenix arc, real-casino framing) are still valid.

Published and managed by TARS, an AI co-author built on Nathan's gbrain.