-- Capture IDs are unique, but multiple captures can still share a camera -- position or the same six image files. Deduplicate before sampling rounds. create or replace function public.game_viewpoint_pool(p_manifest jsonb, p_root text) returns jsonb language sql immutable security invoker set search_path = '' as $$ with by_location as ( select distinct on ( floor((p->>'x')::numeric), floor((p->>'y')::numeric), floor((p->>'z')::numeric) ) p, n from jsonb_array_elements(p_manifest->'viewpoints') with ordinality points(p, n) order by floor((p->>'x')::numeric), floor((p->>'y')::numeric), floor((p->>'z')::numeric), n ), by_media as ( select distinct on ( coalesce(p->>'assetRoot', p_root), coalesce(p->>'assetId', p->>'id') ) p, n from by_location order by coalesce(p->>'assetRoot', p_root), coalesce(p->>'assetId', p->>'id'), n ) select coalesce(jsonb_agg(p order by n), '[]'::jsonb) from by_media; $$; revoke all on function public.game_viewpoint_pool(jsonb, text) from public, anon, authenticated; grant execute on function public.game_viewpoint_pool(jsonb, text) to service_role; create or replace function public.create_game( p_dataset uuid, p_difficulty text, p_owner uuid, p_access_hash text ) returns uuid language plpgsql security invoker set search_path = '' as $$ declare sid uuid; revision uuid; rev public.revisions; vp jsonb; locations jsonb; n integer := 0; settings jsonb; begin settings := public.resolve_difficulty_preset(p_difficulty); revision := public.playable_map_revision(p_dataset, p_owner); select * into rev from public.revisions where id = revision; if not found then raise exception 'Map unavailable'; end if; locations := public.game_viewpoint_pool(rev.manifest, rev.asset_root); if jsonb_array_length(locations) < 5 then raise exception 'Map needs at least five distinct panorama locations'; end if; insert into public.game_sessions( revision_id, owner_id, access_hash, difficulty, difficulty_settings ) values (rev.id, p_owner, p_access_hash, p_difficulty, settings) returning id into sid; for vp in select value from jsonb_array_elements(locations) order by random() limit 5 loop insert into public.game_rounds(session_id, round_number, viewpoint) values (sid, n, vp); n := n + 1; end loop; return sid; end; $$; create or replace function public.create_map_room( p_dataset uuid, p_difficulty text, p_name text, p_limit integer, p_access_hash text, p_owner uuid ) returns uuid language plpgsql security invoker set search_path = '' as $$ declare rid uuid; revision uuid; pid uuid := gen_random_uuid(); rev public.revisions; vp jsonb; locations jsonb; n integer := 0; settings jsonb; begin if p_limit is null or p_limit not between 2 and 16 then raise exception 'Choose between 2 and 16 players'; end if; settings := public.resolve_difficulty_preset(p_difficulty); revision := public.playable_map_revision(p_dataset, p_owner); select * into rev from public.revisions where id = revision; if not found then raise exception 'Map unavailable'; end if; locations := public.game_viewpoint_pool(rev.manifest, rev.asset_root); if jsonb_array_length(locations) < 5 then raise exception 'Map needs at least five distinct panorama locations'; end if; insert into public.multiplayer_rooms( revision_id, host_player_id, difficulty, difficulty_settings, player_limit ) values (rev.id, pid, p_difficulty, settings, p_limit) returning id into rid; insert into public.multiplayer_players(id, room_id, access_hash, display_name) values (pid, rid, p_access_hash, btrim(p_name)); for vp in select value from jsonb_array_elements(locations) order by random() limit 5 loop insert into public.multiplayer_rounds(room_id, round_number, viewpoint) values (rid, n, vp); n := n + 1; end loop; return rid; end; $$;