Files
Forge-Engine/db/__init__.py
T
williamandClaude Sonnet 5 236d6b4b46
Build and deploy / test (push) Failing after 8s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Phase 1 (1/3) : état par joueur — couche db/ et chaîne de rendu
Fil directeur du plan : chaque variable globale et chaque objet de
données gagne un player_id (sentinelle PLAYER_SHARED='__shared__' par
défaut partout — l'aperçu créateur et tous les tests existants
continuent de fonctionner À L'IDENTIQUE, aucune signature appelée sans
argument explicite ne change de comportement), plus un réglage
per_player choisi une fois à la création :
- per_player=1 (par défaut) : chaque joueur a sa propre valeur/ses
  propres lignes.
- per_player=0 : valeur/lignes partagées par tous les joueurs (ex. un
  compteur de visiteurs global, un catalogue commun).

db/global_vars/ : _global_variables passe de UNIQUE(name) à
UNIQUE(name, player_id) — SQLite ne permet pas de modifier une
contrainte UNIQUE via ALTER TABLE, reconstruction de la table détectée
et faite une seule fois (ensure_global_vars_schema.py) pour les jeux
créés avant cette phase. La ligne "modèle" (player_id=PLAYER_SHARED,
créée par le créateur) porte le réglage per_player et sert de valeur
PAR DÉFAUT : la première écriture d'un joueur sur une variable
per_player crée paresseusement SA propre ligne (copiée depuis le
modèle) ; une lecture sans ligne encore écrite retombe sur le modèle
(nouveau resolve_player_key.py). list_global_variables() (tableau de
bord) ne montre toujours que les lignes modèles ; nouveau
list_global_variables_for_player() expose la valeur EFFECTIVE d'un
joueur au runtime (full_game_payload.py).

db/definitions/ + db/rows/ : chaque table d'objet généré
(create_definition.py) gagne une colonne player_id (ADD COLUMN simple,
pas de contrainte UNIQUE en jeu ici) ; _definitions gagne per_player.
Contrairement aux variables, PAS de repli sur une ligne "modèle" pour
les lignes d'un objet per_player — une LISTE n'a pas de valeur par
défaut unique à copier comme un scalaire, un nouvel objet per_player
démarre VIDE pour chaque joueur (nouveau resolve_row_player_key.py).
get_row/update_row/update_row_field/delete_row filtrent aussi par
player_id (pas seulement id) : garde-fou contre un row_id d'un AUTRE
joueur, nécessaire dès qu'un objet per_player sera exposé sur la future
route publique /jouer/<slug>. Migration : nouveau
ensure_player_id_column(slug, table_name), appelé avant toute requête
sur une table d'objet créée avant cette phase.

Chaîne de rendu (screens/elements/list_elements.py ->
screens/rendering/render_element_html.py -> render_repeater.py/
render_jauge.py/resolve_bound_row.py/visibility_condition.py/
filter_repeater_rows.py) : player_id transite dans le ctx déjà utilisé
partout pour "champ en cours" (ctx["_forge_player_id"], même patron que
ctx["_forge_play_mode"], posé une seule fois par list_elements quand
enforce_visibility=True) — pas de nouveau paramètre positionnel à
threader dans chaque fonction, juste une clé de plus dans un mécanisme
déjà en place.

Nouveau tests/test_player_state.py : verrouille à la fois le nouveau
comportement (deux joueurs => valeurs/lignes indépendantes ; per_player=0
=> partagé ; nouveau joueur => valeur par défaut pour une variable, liste
VIDE pour un objet ; get_row ne fuite jamais vers un autre joueur) et la
non-régression de l'aperçu créateur (comportement historique inchangé).

Vérifié : 232 tests passent (9 nouveaux). Reste à faire (prochains
commits) : route publique /jouer/<slug>, identité visiteur (cookie),
bascule "Publier en ligne" dans le tableau de bord.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-30 15:31:08 +02:00

85 lines
4.3 KiB
Python

"""
db — toute la logique base de données du moteur Forge, organisée en un
sous-module par responsabilité (un fichier = une fonction, un dossier = un
domaine) plutôt qu'un seul gros fichier. Ce fichier réexporte l'intégralité
de l'API publique historique : le reste du moteur continue simplement
d'écrire `import db` puis `db.ma_fonction(...)`, sans rien savoir de cette
organisation interne.
Chaque JEU créé a SA PROPRE base SQLite (fichier .db dans son dossier de
projet). Cette base contient :
- des tables "méta" (_definitions, _fields) qui décrivent les objets que
l'utilisateur a définis via l'interface (l'équivalent d'un schéma de
base de données, mais construit à la souris plutôt qu'en SQL) ;
- une VRAIE table SQL par objet défini (obj_<nom>), avec des colonnes du
bon type SQLite selon le type choisi pour chaque champ, y compris des
colonnes de clé étrangère pour les champs de type "relation".
Rien n'est une simulation : créer une "définition d'objet" avec des champs
exécute un vrai CREATE TABLE, et remplir le formulaire généré exécute un
vrai INSERT dans cette table.
"""
from .constants import PROJECTS_DIR, FIELD_TYPES, GLOBAL_VARIABLE_TYPES
from .slugify import slugify
from .table_name_for import table_name_for
from .game_dir import game_dir
from .db_path import db_path
from .connection import connect
from .games.list_games import list_games
from .games.game_meta import game_meta
from .games.create_game import create_game
from .games.update_game_name import update_game_name
from .games.delete_game import delete_game
from .games.move_game import move_game
from .definitions.list_definitions import list_definitions
from .definitions.get_definition import get_definition
from .definitions.create_definition import create_definition
from .definitions.rename_definition import rename_definition
from .definitions.definitions_referencing import definitions_referencing
from .definitions.add_field_to_definition import add_field_to_definition
from .definitions.update_field import update_field
from .definitions.delete_field import delete_field
from .definitions.delete_definition import delete_definition
from .rows.list_rows import list_rows
from .rows.relation_options import relation_options
from .rows.insert_row import insert_row
from .rows.get_row import get_row
from .rows.update_row import update_row
from .rows.update_row_field import update_row_field
from .rows.delete_row import delete_row
from .rows.rows_referencing import rows_referencing
from .global_vars.ensure_global_vars_schema import PLAYER_SHARED
from .global_vars.list_global_variables import list_global_variables
from .global_vars.list_global_variables_for_player import list_global_variables_for_player
from .global_vars.get_global_variable import get_global_variable
from .global_vars.create_global_variable import create_global_variable
from .global_vars.update_global_variable_value import update_global_variable_value
from .global_vars.update_global_variable import update_global_variable
from .global_vars.delete_global_variable import delete_global_variable
from .global_vars.delete_global_variable_by_id import delete_global_variable_by_id
from .custom_events.list_custom_events import list_custom_events
from .custom_events.get_custom_event import get_custom_event
from .custom_events.create_custom_event import create_custom_event
from .custom_events.update_custom_event import update_custom_event
__all__ = [
"PROJECTS_DIR", "FIELD_TYPES", "GLOBAL_VARIABLE_TYPES", "PLAYER_SHARED",
"slugify", "table_name_for", "game_dir", "db_path", "connect",
"list_games", "game_meta", "create_game", "update_game_name", "delete_game", "move_game",
"list_definitions", "get_definition", "create_definition", "rename_definition",
"definitions_referencing", "add_field_to_definition", "update_field", "delete_field",
"delete_definition",
"list_rows", "relation_options", "insert_row", "get_row", "update_row",
"update_row_field", "delete_row", "rows_referencing",
"list_global_variables", "list_global_variables_for_player", "get_global_variable", "create_global_variable",
"update_global_variable_value", "update_global_variable", "delete_global_variable",
"delete_global_variable_by_id",
"list_custom_events", "get_custom_event", "create_custom_event", "update_custom_event",
]