0.1.0-0-pre.3
This commit is contained in:
12
CHANGELOG.md
12
CHANGELOG.md
@@ -1,8 +1,18 @@
|
|||||||
<!-- file: CHANGELOG.md -->
|
<!-- file: CHANGELOG.md -->
|
||||||
<!-- version: 2 -->
|
<!-- version: 3 -->
|
||||||
|
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.1.0-0-pre.3 — 2026-09-16
|
||||||
|
|
||||||
|
- validation utilisateur de `0.1.0-0-pre.2` avec formatage, audits, check, Clippy strict et tests workspace propres ;
|
||||||
|
- introduction de `EngineFrame`, `InputState`, `EngineGame` et `FixedStepRunner` comme première boucle moteur indépendante de SDL3 ;
|
||||||
|
- branchement des deux POC et des deux runners Desktop sur cette boucle commune ;
|
||||||
|
- introduction d'un état explicite `Available` / `Disabled` / `Unsupported` pour les services plateforme optionnels et d'une déclaration de capacités de monétisation ;
|
||||||
|
- documentation de la monétisation optionnelle par plateforme, y compris distinction Android, Web et Desktop ;
|
||||||
|
- documentation du binaire Desktop SDL3 comme défaut et d'une variante Tauri optionnelle sans duplication du gameplay ;
|
||||||
|
- formalisation de la validation Cargo par l'utilisateur, de l'enregistrement de la validation du delta précédent et du passage automatique au delta suivant ou à un `.fix.N` selon le résultat.
|
||||||
|
|
||||||
## 0.1.0-0-pre.2 — 2026-09-15
|
## 0.1.0-0-pre.2 — 2026-09-15
|
||||||
|
|
||||||
- intégration de l'architecture de référence dans une documentation thématique durable ;
|
- intégration de l'architecture de référence dans une documentation thématique durable ;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# file: Cargo.toml
|
# file: Cargo.toml
|
||||||
# version: 3
|
# version: 4
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "3"
|
resolver = "3"
|
||||||
@@ -14,7 +14,7 @@ members = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.1.0-0-pre.2"
|
version = "0.1.0-0-pre.3"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://git.sasedev.com/Sasedev/games"
|
repository = "https://git.sasedev.com/Sasedev/games"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: README.md -->
|
<!-- file: README.md -->
|
||||||
<!-- version: 2 -->
|
<!-- version: 3 -->
|
||||||
|
|
||||||
# games.sasedev
|
# games.sasedev
|
||||||
|
|
||||||
@@ -14,6 +14,8 @@ Workspace expérimental puis productif pour des jeux multiplateformes principale
|
|||||||
- assets hors des crates sous `assets/` ;
|
- assets hors des crates sous `assets/` ;
|
||||||
- `assets/common/` pour les ressources mutualisées et un répertoire par jeu pour les ressources spécifiques ;
|
- `assets/common/` pour les ressources mutualisées et un répertoire par jeu pour les ressources spécifiques ;
|
||||||
- frontend Android sous `Android/`, en Java, avec une partie commune et une partie spécifique par jeu ;
|
- frontend Android sous `Android/`, en Java, avec une partie commune et une partie spécifique par jeu ;
|
||||||
|
- monétisation optionnelle et spécifique à chaque plateforme/distribution ;
|
||||||
|
- runner Desktop natif SDL3 par défaut, avec variante Tauri uniquement si un besoin futur la justifie ;
|
||||||
- documentation sous `docs/` ;
|
- documentation sous `docs/` ;
|
||||||
- règles normatives indexées par `RULES.md` ;
|
- règles normatives indexées par `RULES.md` ;
|
||||||
- livraison par archives delta versionnées ;
|
- livraison par archives delta versionnées ;
|
||||||
@@ -21,7 +23,7 @@ Workspace expérimental puis productif pour des jeux multiplateformes principale
|
|||||||
|
|
||||||
## Baseline
|
## Baseline
|
||||||
|
|
||||||
Version courante : `0.1.0-0-pre.2`.
|
Version courante : `0.1.0-0-pre.3`.
|
||||||
|
|
||||||
Les deux premiers jeux sont des POC structurels : `game-reflex-poc` et `game-snake-poc`. Ils existent d'abord pour valider les frontières du workspace, le moteur, les assets et le packaging multiplateforme.
|
Les deux premiers jeux sont des POC structurels : `game-reflex-poc` et `game-snake-poc`. Ils existent d'abord pour valider les frontières du workspace, le moteur, les assets et le packaging multiplateforme.
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: ROADMAP.md -->
|
<!-- file: ROADMAP.md -->
|
||||||
<!-- version: 2 -->
|
<!-- version: 3 -->
|
||||||
|
|
||||||
# Roadmap
|
# Roadmap
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
- [x] `0-pre.1` — squelette du workspace, règles, architecture, versions, assets, Java Android commun/spécifique et deux crates POC.
|
- [x] `0-pre.1` — squelette du workspace, règles, architecture, versions, assets, Java Android commun/spécifique et deux crates POC.
|
||||||
- [x] `0-pre.2` — consolidation documentaire issue de l'architecture de référence, runners Desktop par jeu et politique normative des commandes.
|
- [x] `0-pre.2` — consolidation documentaire issue de l'architecture de référence, runners Desktop par jeu et politique normative des commandes.
|
||||||
- [ ] `0-pre.3` — première boucle moteur exécutable Desktop : temps, input abstrait et état de jeu minimal via les runners.
|
- [x] `0-pre.3` — première boucle moteur exécutable Desktop : temps, input abstrait, capacités de monétisation optionnelles et état de jeu minimal via les runners.
|
||||||
- [ ] `0-pre.4` — intégration SDL3 Desktop réelle et premier rendu POC Reflex.
|
- [ ] `0-pre.4` — intégration SDL3 Desktop réelle et premier rendu POC Reflex.
|
||||||
- [ ] `0-pre.5` — projet Gradle Android exécutable, SDL3 AAR/NDK, compilation Rust `cdylib`, lancement sur appareil/émulateur.
|
- [ ] `0-pre.5` — projet Gradle Android exécutable, SDL3 AAR/NDK, compilation Rust `cdylib`, lancement sur appareil/émulateur.
|
||||||
- [ ] `0-pre.6` — bridge Java/JNI minimal et input tactile Android.
|
- [ ] `0-pre.6` — bridge Java/JNI minimal et input tactile Android.
|
||||||
|
|||||||
2
RULES.md
2
RULES.md
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: RULES.md -->
|
<!-- file: RULES.md -->
|
||||||
<!-- version: 2 -->
|
<!-- version: 3 -->
|
||||||
|
|
||||||
# Index normatif games.sasedev
|
# Index normatif games.sasedev
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# file: crates/apps/game-reflex-poc-desktop/Cargo.toml
|
# file: crates/apps/game-reflex-poc-desktop/Cargo.toml
|
||||||
# version: 1
|
# version: 2
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "game-reflex-poc-desktop"
|
name = "game-reflex-poc-desktop"
|
||||||
@@ -11,6 +11,8 @@ authors.workspace = true
|
|||||||
publish.workspace = true
|
publish.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
engine-v1-common = { path = "../../engines/engine-v1-common" }
|
||||||
|
engine-v1-platform-api = { path = "../../engines/engine-v1-platform-api" }
|
||||||
game-reflex-poc = { path = "../../games/game-reflex-poc" }
|
game-reflex-poc = { path = "../../games/game-reflex-poc" }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/apps/game-reflex-poc-desktop/src/main.rs
|
// file: crates/apps/game-reflex-poc-desktop/src/main.rs
|
||||||
// version: 1
|
// version: 2
|
||||||
|
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![deny(unreachable_pub)]
|
#![deny(unreachable_pub)]
|
||||||
@@ -7,10 +7,16 @@
|
|||||||
|
|
||||||
//! Desktop development runner for the Reflex POC game library.
|
//! Desktop development runner for the Reflex POC game library.
|
||||||
//!
|
//!
|
||||||
//! This binary is intentionally minimal until the executable SDL3 runtime is introduced.
|
//! This binary exercises the platform-independent fixed-step loop. SDL3 windowing and physical input mapping are introduced in the next dedicated delta.
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let state = game_reflex_poc::ReflexState::new();
|
let _monetization = engine_v1_platform_api::MonetizationCapabilities::disabled();
|
||||||
println!("Reflex POC desktop runner: score={}", state.score());
|
let input = engine_v1_common::InputState::none().with_action(engine_v1_common::GameAction::Primary, true);
|
||||||
|
let mut runner = engine_v1_common::FixedStepRunner::new(std::time::Duration::from_millis(16));
|
||||||
|
let mut state = game_reflex_poc::ReflexState::new();
|
||||||
|
for _ in 0..3 {
|
||||||
|
runner.tick(&mut state, input);
|
||||||
|
}
|
||||||
|
println!("Reflex POC desktop runner: score={} frames={}", state.score(), runner.next_frame_index());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# file: crates/apps/game-snake-poc-desktop/Cargo.toml
|
# file: crates/apps/game-snake-poc-desktop/Cargo.toml
|
||||||
# version: 1
|
# version: 2
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "game-snake-poc-desktop"
|
name = "game-snake-poc-desktop"
|
||||||
@@ -11,6 +11,8 @@ authors.workspace = true
|
|||||||
publish.workspace = true
|
publish.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
engine-v1-common = { path = "../../engines/engine-v1-common" }
|
||||||
|
engine-v1-platform-api = { path = "../../engines/engine-v1-platform-api" }
|
||||||
game-snake-poc = { path = "../../games/game-snake-poc" }
|
game-snake-poc = { path = "../../games/game-snake-poc" }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/apps/game-snake-poc-desktop/src/main.rs
|
// file: crates/apps/game-snake-poc-desktop/src/main.rs
|
||||||
// version: 1
|
// version: 2
|
||||||
|
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![deny(unreachable_pub)]
|
#![deny(unreachable_pub)]
|
||||||
@@ -7,10 +7,16 @@
|
|||||||
|
|
||||||
//! Desktop development runner for the Snake POC game library.
|
//! Desktop development runner for the Snake POC game library.
|
||||||
//!
|
//!
|
||||||
//! This binary is intentionally minimal until the executable SDL3 runtime is introduced.
|
//! This binary exercises the platform-independent fixed-step loop. SDL3 windowing and physical input mapping are introduced in the next dedicated delta.
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let state = game_snake_poc::SnakeState::new();
|
let _monetization = engine_v1_platform_api::MonetizationCapabilities::disabled();
|
||||||
println!("Snake POC desktop runner: length={}", state.length());
|
let input = engine_v1_common::InputState::none().with_action(engine_v1_common::GameAction::Primary, true);
|
||||||
|
let mut runner = engine_v1_common::FixedStepRunner::new(std::time::Duration::from_millis(16));
|
||||||
|
let mut state = game_snake_poc::SnakeState::new();
|
||||||
|
for _ in 0..3 {
|
||||||
|
runner.tick(&mut state, input);
|
||||||
|
}
|
||||||
|
println!("Snake POC desktop runner: length={} frames={}", state.length(), runner.next_frame_index());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
36
crates/engines/engine-v1-common/src/frame.rs
Normal file
36
crates/engines/engine-v1-common/src/frame.rs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
// file: crates/engines/engine-v1-common/src/frame.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
/// Immutable timing information associated with one engine update.
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub struct EngineFrame {
|
||||||
|
index: u64,
|
||||||
|
delta: std::time::Duration,
|
||||||
|
elapsed: std::time::Duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EngineFrame {
|
||||||
|
/// Creates timing information for one update.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn new(index: u64, delta: std::time::Duration, elapsed: std::time::Duration) -> Self {
|
||||||
|
return Self { index, delta, elapsed };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the zero-based update index.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn index(self) -> u64 {
|
||||||
|
return self.index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the duration represented by this update.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn delta(self) -> std::time::Duration {
|
||||||
|
return self.delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the accumulated simulated duration before this update completes.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn elapsed(self) -> std::time::Duration {
|
||||||
|
return self.elapsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
83
crates/engines/engine-v1-common/src/game_loop.rs
Normal file
83
crates/engines/engine-v1-common/src/game_loop.rs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
// file: crates/engines/engine-v1-common/src/game_loop.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
/// Minimal update contract implemented by a game state consumed by engine V1.
|
||||||
|
pub trait EngineGame {
|
||||||
|
/// Advances the game by one engine update using platform-independent input.
|
||||||
|
fn update(&mut self, frame: crate::EngineFrame, input: crate::InputState);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deterministic fixed-step driver used before and underneath platform event loops.
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub struct FixedStepRunner {
|
||||||
|
delta: std::time::Duration,
|
||||||
|
next_frame_index: u64,
|
||||||
|
elapsed: std::time::Duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FixedStepRunner {
|
||||||
|
/// Creates a fixed-step runner using the supplied update duration.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn new(delta: std::time::Duration) -> Self {
|
||||||
|
return Self { delta, next_frame_index: 0, elapsed: std::time::Duration::ZERO };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the configured fixed update duration.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn delta(self) -> std::time::Duration {
|
||||||
|
return self.delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the index that will be assigned to the next update.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn next_frame_index(self) -> u64 {
|
||||||
|
return self.next_frame_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the accumulated simulated time.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn elapsed(self) -> std::time::Duration {
|
||||||
|
return self.elapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Advances one game state by one deterministic update.
|
||||||
|
pub fn tick<G>(&mut self, game: &mut G, input: crate::InputState)
|
||||||
|
where
|
||||||
|
G: crate::EngineGame,
|
||||||
|
{
|
||||||
|
let frame = crate::EngineFrame::new(self.next_frame_index, self.delta, self.elapsed);
|
||||||
|
game.update(frame, input);
|
||||||
|
self.next_frame_index = self.next_frame_index.saturating_add(1);
|
||||||
|
self.elapsed = self.elapsed.saturating_add(self.delta);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod unit_tests {
|
||||||
|
#[derive(Default)]
|
||||||
|
struct CountingGame {
|
||||||
|
updates: u64,
|
||||||
|
last_frame: Option<crate::EngineFrame>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::EngineGame for CountingGame {
|
||||||
|
fn update(&mut self, frame: crate::EngineFrame, _input: crate::InputState) {
|
||||||
|
self.updates = self.updates.saturating_add(1);
|
||||||
|
self.last_frame = Some(frame);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fixed_step_runner_advances_index_and_elapsed_time() {
|
||||||
|
let mut game = CountingGame::default();
|
||||||
|
let mut runner = crate::FixedStepRunner::new(std::time::Duration::from_millis(16));
|
||||||
|
runner.tick(&mut game, crate::InputState::none());
|
||||||
|
runner.tick(&mut game, crate::InputState::none());
|
||||||
|
assert_eq!(game.updates, 2);
|
||||||
|
assert_eq!(runner.next_frame_index(), 2);
|
||||||
|
assert_eq!(runner.elapsed(), std::time::Duration::from_millis(32));
|
||||||
|
assert_eq!(game.last_frame.map(crate::EngineFrame::index), Some(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
61
crates/engines/engine-v1-common/src/input.rs
Normal file
61
crates/engines/engine-v1-common/src/input.rs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
// file: crates/engines/engine-v1-common/src/input.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
/// Platform-independent snapshot of logical input actions for one update.
|
||||||
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||||
|
pub struct InputState {
|
||||||
|
left: bool,
|
||||||
|
right: bool,
|
||||||
|
up: bool,
|
||||||
|
down: bool,
|
||||||
|
primary: bool,
|
||||||
|
secondary: bool,
|
||||||
|
pause: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InputState {
|
||||||
|
/// Creates a snapshot with no active action.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn none() -> Self {
|
||||||
|
return Self { left: false, right: false, up: false, down: false, primary: false, secondary: false, pause: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a copy with the selected logical action set to the requested state.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn with_action(mut self, action: crate::GameAction, active: bool) -> Self {
|
||||||
|
match action {
|
||||||
|
crate::GameAction::Left => self.left = active,
|
||||||
|
crate::GameAction::Right => self.right = active,
|
||||||
|
crate::GameAction::Up => self.up = active,
|
||||||
|
crate::GameAction::Down => self.down = active,
|
||||||
|
crate::GameAction::Primary => self.primary = active,
|
||||||
|
crate::GameAction::Secondary => self.secondary = active,
|
||||||
|
crate::GameAction::Pause => self.pause = active,
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reports whether the selected logical action is active.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn is_active(self, action: crate::GameAction) -> bool {
|
||||||
|
return match action {
|
||||||
|
crate::GameAction::Left => self.left,
|
||||||
|
crate::GameAction::Right => self.right,
|
||||||
|
crate::GameAction::Up => self.up,
|
||||||
|
crate::GameAction::Down => self.down,
|
||||||
|
crate::GameAction::Primary => self.primary,
|
||||||
|
crate::GameAction::Secondary => self.secondary,
|
||||||
|
crate::GameAction::Pause => self.pause,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod unit_tests {
|
||||||
|
#[test]
|
||||||
|
fn action_state_is_independent() {
|
||||||
|
let state = crate::InputState::none().with_action(crate::GameAction::Primary, true);
|
||||||
|
assert!(state.is_active(crate::GameAction::Primary));
|
||||||
|
assert!(!state.is_active(crate::GameAction::Secondary));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/engines/engine-v1-common/src/lib.rs
|
// file: crates/engines/engine-v1-common/src/lib.rs
|
||||||
// version: 1
|
// version: 2
|
||||||
|
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![deny(unreachable_pub)]
|
#![deny(unreachable_pub)]
|
||||||
@@ -8,6 +8,17 @@
|
|||||||
//! Common, platform-independent primitives for engine generation V1.
|
//! Common, platform-independent primitives for engine generation V1.
|
||||||
|
|
||||||
mod action;
|
mod action;
|
||||||
|
mod frame;
|
||||||
|
mod game_loop;
|
||||||
|
mod input;
|
||||||
|
|
||||||
/// Re-export of the canonical logical input action used by engine V1 games.
|
/// Re-export of the canonical logical input action used by engine V1 games.
|
||||||
pub use self::action::GameAction;
|
pub use self::action::GameAction;
|
||||||
|
/// Re-export of immutable timing information for one engine update.
|
||||||
|
pub use self::frame::EngineFrame;
|
||||||
|
/// Re-export of the minimal game update contract consumed by engine V1.
|
||||||
|
pub use self::game_loop::EngineGame;
|
||||||
|
/// Re-export of the deterministic fixed-step driver.
|
||||||
|
pub use self::game_loop::FixedStepRunner;
|
||||||
|
/// Re-export of the platform-independent logical input snapshot.
|
||||||
|
pub use self::input::InputState;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/engines/engine-v1-platform-api/src/lib.rs
|
// file: crates/engines/engine-v1-platform-api/src/lib.rs
|
||||||
// version: 1
|
// version: 2
|
||||||
|
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![deny(unreachable_pub)]
|
#![deny(unreachable_pub)]
|
||||||
@@ -9,5 +9,9 @@
|
|||||||
|
|
||||||
mod service;
|
mod service;
|
||||||
|
|
||||||
|
/// Re-export of optional monetization capability declarations.
|
||||||
|
pub use self::service::MonetizationCapabilities;
|
||||||
/// Re-export of the minimal platform service capability enumeration.
|
/// Re-export of the minimal platform service capability enumeration.
|
||||||
pub use self::service::PlatformCapability;
|
pub use self::service::PlatformCapability;
|
||||||
|
/// Re-export of optional platform service availability states.
|
||||||
|
pub use self::service::PlatformServiceAvailability;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/engines/engine-v1-platform-api/src/service.rs
|
// file: crates/engines/engine-v1-platform-api/src/service.rs
|
||||||
// version: 1
|
// version: 2
|
||||||
|
|
||||||
/// Platform capabilities that may have different implementations per target.
|
/// Platform capabilities that may have different implementations per target.
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
@@ -15,3 +15,57 @@ pub enum PlatformCapability {
|
|||||||
/// Online leaderboard integration.
|
/// Online leaderboard integration.
|
||||||
Leaderboard,
|
Leaderboard,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Availability state of one optional platform service.
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub enum PlatformServiceAvailability {
|
||||||
|
/// The platform implementation is present and may be used.
|
||||||
|
Available,
|
||||||
|
/// The platform could support the service, but this build intentionally disables it.
|
||||||
|
Disabled,
|
||||||
|
/// The current target or distribution does not provide this service implementation.
|
||||||
|
Unsupported,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Declarative availability of optional monetization services for one build.
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub struct MonetizationCapabilities {
|
||||||
|
advertising: PlatformServiceAvailability,
|
||||||
|
billing: PlatformServiceAvailability,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MonetizationCapabilities {
|
||||||
|
/// Creates a monetization capability declaration.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn new(advertising: PlatformServiceAvailability, billing: PlatformServiceAvailability) -> Self {
|
||||||
|
return Self { advertising, billing };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a declaration with monetization intentionally disabled.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn disabled() -> Self {
|
||||||
|
return Self::new(PlatformServiceAvailability::Disabled, PlatformServiceAvailability::Disabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns advertising availability.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn advertising(self) -> PlatformServiceAvailability {
|
||||||
|
return self.advertising;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns billing availability.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn billing(self) -> PlatformServiceAvailability {
|
||||||
|
return self.billing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod unit_tests {
|
||||||
|
#[test]
|
||||||
|
fn disabled_monetization_disables_both_services() {
|
||||||
|
let capabilities = crate::MonetizationCapabilities::disabled();
|
||||||
|
assert_eq!(capabilities.advertising(), crate::PlatformServiceAvailability::Disabled);
|
||||||
|
assert_eq!(capabilities.billing(), crate::PlatformServiceAvailability::Disabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/games/game-reflex-poc/src/state.rs
|
// file: crates/games/game-reflex-poc/src/state.rs
|
||||||
// version: 1
|
// version: 2
|
||||||
|
|
||||||
/// Minimal deterministic state used to validate the first game boundary.
|
/// Minimal deterministic state used to validate the first game boundary.
|
||||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||||
@@ -26,6 +26,15 @@ impl ReflexState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl engine_v1_common::EngineGame for ReflexState {
|
||||||
|
fn update(&mut self, _frame: engine_v1_common::EngineFrame, input: engine_v1_common::InputState) {
|
||||||
|
if input.is_active(engine_v1_common::GameAction::Primary) {
|
||||||
|
self.register_success();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod unit_tests {
|
mod unit_tests {
|
||||||
#[test]
|
#[test]
|
||||||
@@ -34,4 +43,16 @@ mod unit_tests {
|
|||||||
state.register_success();
|
state.register_success();
|
||||||
assert_eq!(state.score(), 1);
|
assert_eq!(state.score(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn engine_update_maps_primary_action_to_success() {
|
||||||
|
let mut state = crate::ReflexState::new();
|
||||||
|
let input = engine_v1_common::InputState::none().with_action(engine_v1_common::GameAction::Primary, true);
|
||||||
|
engine_v1_common::EngineGame::update(
|
||||||
|
&mut state,
|
||||||
|
engine_v1_common::EngineFrame::new(0, std::time::Duration::from_millis(16), std::time::Duration::ZERO),
|
||||||
|
input,
|
||||||
|
);
|
||||||
|
assert_eq!(state.score(), 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/games/game-snake-poc/src/state.rs
|
// file: crates/games/game-snake-poc/src/state.rs
|
||||||
// version: 1
|
// version: 2
|
||||||
|
|
||||||
/// Minimal Snake state used to validate reusable engine dependencies.
|
/// Minimal Snake state used to validate reusable engine dependencies.
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
@@ -32,6 +32,15 @@ impl SnakeState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl engine_v1_common::EngineGame for SnakeState {
|
||||||
|
fn update(&mut self, _frame: engine_v1_common::EngineFrame, input: engine_v1_common::InputState) {
|
||||||
|
if input.is_active(engine_v1_common::GameAction::Primary) {
|
||||||
|
self.grow();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod unit_tests {
|
mod unit_tests {
|
||||||
#[test]
|
#[test]
|
||||||
@@ -40,4 +49,16 @@ mod unit_tests {
|
|||||||
state.grow();
|
state.grow();
|
||||||
assert_eq!(state.length(), 2);
|
assert_eq!(state.length(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn engine_update_maps_primary_action_to_growth() {
|
||||||
|
let mut state = crate::SnakeState::new();
|
||||||
|
let input = engine_v1_common::InputState::none().with_action(engine_v1_common::GameAction::Primary, true);
|
||||||
|
engine_v1_common::EngineGame::update(
|
||||||
|
&mut state,
|
||||||
|
engine_v1_common::EngineFrame::new(0, std::time::Duration::from_millis(16), std::time::Duration::ZERO),
|
||||||
|
input,
|
||||||
|
);
|
||||||
|
assert_eq!(state.length(), 2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
98
deltas/0.1.0/0-pre.3.md
Normal file
98
deltas/0.1.0/0-pre.3.md
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
<!-- file: deltas/0.1.0/0-pre.3.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta 0.1.0-0-pre.3
|
||||||
|
|
||||||
|
## Base
|
||||||
|
|
||||||
|
Base déclarée : `0.1.0-0-pre.2` validée par l'utilisateur le 2026-09-16.
|
||||||
|
|
||||||
|
## Validation du delta précédent
|
||||||
|
|
||||||
|
L'utilisateur a exécuté la gate complète suivante sur `0.1.0-0-pre.2` :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo fmt --all -- --check
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py
|
||||||
|
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates Android deltas
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||||
|
cargo test --workspace --all-targets --all-features
|
||||||
|
```
|
||||||
|
|
||||||
|
Résultat communiqué : propre. Les audits ont annoncé `1 table(s), 28 file(s)`, tous les packages du workspace ont passé `cargo check` et Clippy strict, et les tests Reflex/Snake ont chacun passé leur test unitaire.
|
||||||
|
|
||||||
|
`0.1.0-0-pre.2` est donc accepté comme base de ce delta.
|
||||||
|
|
||||||
|
## Objectifs
|
||||||
|
|
||||||
|
- introduire la première boucle moteur commune exécutable sans dépendre encore de SDL3 ;
|
||||||
|
- représenter temps/frame et input logique indépendamment de la plateforme ;
|
||||||
|
- faire consommer cette boucle par les deux POC et les deux runners Desktop ;
|
||||||
|
- rendre explicite le caractère optionnel de la monétisation ;
|
||||||
|
- distinguer les modèles Android, Web et Desktop sans imposer un backend publicitaire universel ;
|
||||||
|
- préciser le rôle du runner Desktop SDL3 et d'une éventuelle variante Tauri ;
|
||||||
|
- formaliser la validation Cargo par l'utilisateur et l'enchaînement automatique delta/fix.
|
||||||
|
|
||||||
|
## Code
|
||||||
|
|
||||||
|
`engine-v1-common` ajoute :
|
||||||
|
|
||||||
|
- `EngineFrame` ;
|
||||||
|
- `InputState` ;
|
||||||
|
- `EngineGame` ;
|
||||||
|
- `FixedStepRunner`.
|
||||||
|
|
||||||
|
Les deux états POC implémentent `EngineGame`. Leur action `Primary` déclenche respectivement un succès Reflex ou une croissance Snake, ce qui fournit un comportement volontairement minimal mais observable pour les runners.
|
||||||
|
|
||||||
|
Les deux runners Desktop exécutent trois ticks fixes de 16 ms avec l'action `Primary` active. Ils ne créent toujours ni fenêtre ni renderer : l'intégration SDL3 réelle reste `0-pre.4`.
|
||||||
|
|
||||||
|
## Monétisation optionnelle
|
||||||
|
|
||||||
|
`engine-v1-platform-api` ajoute :
|
||||||
|
|
||||||
|
- `PlatformServiceAvailability::{Available, Disabled, Unsupported}` ;
|
||||||
|
- `MonetizationCapabilities`.
|
||||||
|
|
||||||
|
Le runner Desktop déclare actuellement la monétisation désactivée. Android pourra fournir une implémentation publicitaire/billing, Web pourra utiliser des backends Web distincts, et Desktop peut rester sans publicité.
|
||||||
|
|
||||||
|
## Desktop natif et Tauri
|
||||||
|
|
||||||
|
Le binaire SDL3 natif reste la distribution Desktop de jeu par défaut. Une variante `<game>-desktop-tauri` pourra être introduite ultérieurement pour un besoin réel de WebView/launcher/interface riche ; elle devra consommer la même crate lib de jeu et ne constituera jamais une duplication du gameplay.
|
||||||
|
|
||||||
|
## Validation exécutée pendant la préparation
|
||||||
|
|
||||||
|
Les audits Python et contrôles structurels disponibles dans l'environnement de génération sont exécutés avant livraison. Les commandes Cargo finales ne sont pas exécutées par le générateur sauf demande explicite de l'utilisateur.
|
||||||
|
|
||||||
|
## Commandes de validation à exécuter par l'utilisateur
|
||||||
|
|
||||||
|
Gate complète obligatoire :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo fmt --all -- --check
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py
|
||||||
|
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates Android deltas
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||||
|
cargo test --workspace --all-targets --all-features
|
||||||
|
```
|
||||||
|
|
||||||
|
Smokes Desktop requis pour ce delta, car la boucle exécutable a changé :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo run -p game-reflex-poc-desktop
|
||||||
|
cargo run -p game-snake-poc-desktop
|
||||||
|
```
|
||||||
|
|
||||||
|
Résultats fonctionnels attendus des smokes :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Reflex POC desktop runner: score=3 frames=3
|
||||||
|
Snake POC desktop runner: length=4 frames=3
|
||||||
|
```
|
||||||
|
|
||||||
|
## Règle de transition
|
||||||
|
|
||||||
|
Si toutes ces validations sont propres, `0.1.0-0-pre.3` est accepté et le travail passe automatiquement à `0.1.0-0-pre.4`, sauf avis contraire.
|
||||||
|
|
||||||
|
Si une validation échoue, ne pas passer à `0-pre.4` : produire `0.1.0-0-pre.3.fix.1`, puis incrémenter le suffixe de fix si nécessaire jusqu'à validation.
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/000-README.md -->
|
<!-- file: docs/000-README.md -->
|
||||||
<!-- version: 2 -->
|
<!-- version: 3 -->
|
||||||
|
|
||||||
# Documentation games.sasedev
|
# Documentation games.sasedev
|
||||||
|
|
||||||
@@ -42,3 +42,5 @@ Voir [`../RULES.md`](../RULES.md), notamment [`rules/RULES_COMMANDS.md`](rules/R
|
|||||||
## Validation
|
## Validation
|
||||||
|
|
||||||
- [`validation/001-VALIDATION_GATES.md`](validation/001-VALIDATION_GATES.md) — gates manuelles du workspace.
|
- [`validation/001-VALIDATION_GATES.md`](validation/001-VALIDATION_GATES.md) — gates manuelles du workspace.
|
||||||
|
|
||||||
|
- [`development/002-DESKTOP_DISTRIBUTIONS.md`](development/002-DESKTOP_DISTRIBUTIONS.md) — distributions Desktop natives et variante Tauri optionnelle.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/development/001-DESKTOP_RUNNERS.md -->
|
<!-- file: docs/development/001-DESKTOP_RUNNERS.md -->
|
||||||
<!-- version: 1 -->
|
<!-- version: 2 -->
|
||||||
|
|
||||||
# Runners Desktop de développement
|
# Runners Desktop de développement
|
||||||
|
|
||||||
@@ -29,6 +29,10 @@ La crate lib du jeu porte gameplay, règles, état, progression et interfaces n
|
|||||||
|
|
||||||
Le runner Desktop ne remplace pas les tests Android. Lifecycle, JNI, tactile réel, Ads, Billing, haptique, permissions et APIs Play nécessitent une validation sur la cible Android.
|
Le runner Desktop ne remplace pas les tests Android. Lifecycle, JNI, tactile réel, Ads, Billing, haptique, permissions et APIs Play nécessitent une validation sur la cible Android.
|
||||||
|
|
||||||
## Baseline `0.1.0-0-pre.2`
|
## Baseline `0.1.0-0-pre.3`
|
||||||
|
|
||||||
Les deux runners sont volontairement minimaux : ils lient les crates de jeu et permettent de vérifier leur exécution locale. La vraie boucle SDL3 Desktop reste planifiée dans une tranche dédiée.
|
Les deux runners utilisent désormais le `FixedStepRunner` commun et des `InputState` logiques pour exercer la première boucle moteur indépendante de SDL. Ils déclarent explicitement la monétisation désactivée pour leur smoke Desktop.
|
||||||
|
|
||||||
|
La traduction clavier/souris vers `InputState`, la fenêtre et le rendu SDL3 restent planifiés pour `0-pre.4`.
|
||||||
|
|
||||||
|
La possibilité d'une variante Desktop Tauri est documentée séparément dans `002-DESKTOP_DISTRIBUTIONS.md` et reste optionnelle.
|
||||||
|
|||||||
53
docs/development/002-DESKTOP_DISTRIBUTIONS.md
Normal file
53
docs/development/002-DESKTOP_DISTRIBUTIONS.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<!-- file: docs/development/002-DESKTOP_DISTRIBUTIONS.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Distributions Desktop
|
||||||
|
|
||||||
|
## Principe
|
||||||
|
|
||||||
|
Le terme `Desktop` peut désigner plusieurs hôtes différents autour d'une même crate de jeu. Ces hôtes ne sont pas tous obligatoires et ne doivent jamais provoquer une duplication du gameplay.
|
||||||
|
|
||||||
|
## Binaire natif SDL3
|
||||||
|
|
||||||
|
Le runner natif est la voie Desktop canonique pour les jeux eux-mêmes :
|
||||||
|
|
||||||
|
```text
|
||||||
|
game lib
|
||||||
|
↓
|
||||||
|
engine V1
|
||||||
|
↓
|
||||||
|
SDL3
|
||||||
|
↓
|
||||||
|
native desktop binary
|
||||||
|
```
|
||||||
|
|
||||||
|
Il fournit la boucle de fenêtre, le rendu, l'audio et les entrées clavier/souris/gamepad nécessaires au jeu. C'est aussi la voie privilégiée pour les itérations rapides depuis le poste de développement.
|
||||||
|
|
||||||
|
Convention initiale :
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/apps/<game>-desktop
|
||||||
|
```
|
||||||
|
|
||||||
|
## Variante Tauri optionnelle
|
||||||
|
|
||||||
|
Une variante Tauri peut être ajoutée si un besoin réel apparaît : launcher, gestion de compte, configuration riche, catalogue de jeux, téléchargement de contenu, interface Web complexe ou application compagnon.
|
||||||
|
|
||||||
|
Tauri utilise un backend Rust et une interface rendue dans une WebView. Cette architecture est différente du runner SDL3 natif et n'est pas une dépendance obligatoire du moteur.
|
||||||
|
|
||||||
|
Si un jeu possède réellement deux distributions Desktop, la convention envisagée est :
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/apps/<game>-desktop
|
||||||
|
crates/apps/<game>-desktop-tauri
|
||||||
|
```
|
||||||
|
|
||||||
|
La seconde n'est créée que lorsqu'elle apporte une fonctionnalité qui justifie son coût de maintenance.
|
||||||
|
|
||||||
|
## Règle de non-duplication
|
||||||
|
|
||||||
|
Les deux hôtes, s'ils coexistent, consomment la même crate lib de jeu. Le gameplay, le scoring, les règles et la progression ne sont jamais recopiés dans Tauri ou dans le runner natif.
|
||||||
|
|
||||||
|
## Choix par défaut
|
||||||
|
|
||||||
|
Pour les POC de jeux 2D, le binaire natif SDL3 est la cible Desktop par défaut. Tauri reste une extension future et ne fait pas partie de la gate initiale du moteur.
|
||||||
@@ -1,45 +1,72 @@
|
|||||||
<!-- file: docs/monetization/001-MONETIZATION.md -->
|
<!-- file: docs/monetization/001-MONETIZATION.md -->
|
||||||
<!-- version: 1 -->
|
<!-- version: 2 -->
|
||||||
|
|
||||||
# Monétisation
|
# Monétisation
|
||||||
|
|
||||||
## Principe d'abstraction
|
## Principe
|
||||||
|
|
||||||
Le gameplay ne dépend pas directement d'AdMob, AppLovin, LevelPlay ou d'un autre fournisseur. Il consomme un contrat de publicité ou de billing, tandis que l'implémentation Android passe par Java/JNI et les SDK natifs Android.
|
La monétisation est une capacité optionnelle de plateforme et de distribution. Aucun jeu, aucune génération du moteur et aucun runner ne doit supposer qu'une publicité, un achat intégré ou un fournisseur particulier est disponible.
|
||||||
|
|
||||||
|
Le gameplay dépend uniquement de contrats abstraits et doit rester fonctionnel lorsque la monétisation est `Disabled` ou `Unsupported`.
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Rust game
|
Game
|
||||||
↓
|
↓
|
||||||
Advertising/Billing API
|
Monetization contract
|
||||||
↓
|
↓
|
||||||
platform implementation
|
capability state
|
||||||
↓
|
├── Available -> backend plateforme
|
||||||
Java/JNI Android
|
├── Disabled -> fonctionnalité volontairement coupée
|
||||||
↓
|
└── Unsupported -> aucune implémentation pour cette cible
|
||||||
provider SDK
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Android
|
||||||
|
|
||||||
|
Android est la cible prioritaire pour la monétisation publicitaire des premiers jeux. L'implémentation prévue passe par Java/JNI et un fournisseur ou médiateur Android tel qu'AdMob, AppLovin MAX ou LevelPlay.
|
||||||
|
|
||||||
|
La présence des SDK publicitaires n'est pas obligatoire dans toutes les variantes Android. Un build de test, une édition payante ou une distribution particulière peut désactiver la publicité tout en réutilisant exactement la même crate de jeu.
|
||||||
|
|
||||||
|
## Web
|
||||||
|
|
||||||
|
La monétisation Web est distincte de la monétisation Android. Un frontend Web peut utiliser ultérieurement un fournisseur publicitaire destiné aux sites ou contenus Web, par exemple une solution de type AdSense, mais il ne doit pas être traité comme une simple réutilisation du SDK AdMob Android.
|
||||||
|
|
||||||
|
Le backend Web de monétisation reste optionnel et n'est introduit que lorsqu'une vraie cible Web est mise en œuvre et que ses contraintes de politique, consentement, sandbox navigateur et expérience utilisateur ont été validées.
|
||||||
|
|
||||||
|
## Desktop natif
|
||||||
|
|
||||||
|
Aucun fournisseur publicitaire Desktop générique n'est imposé par le framework. La baseline Desktop native considère la publicité comme désactivée par défaut.
|
||||||
|
|
||||||
|
Une distribution Desktop peut préférer d'autres modèles : achat unique, donation, DLC, contenu premium, distribution via un store, cross-promotion externe ou absence totale de monétisation.
|
||||||
|
|
||||||
|
Si un fournisseur publicitaire Desktop pertinent est retenu ultérieurement, il devra être ajouté derrière le même contrat optionnel sans faire entrer son SDK dans les crates de gameplay.
|
||||||
|
|
||||||
|
## Desktop Tauri
|
||||||
|
|
||||||
|
Un éventuel frontend Tauri peut techniquement afficher du contenu Web dans une WebView, mais cela ne transforme pas automatiquement une application Desktop en inventaire publicitaire Web autorisé. Toute solution publicitaire utilisée dans une WebView devra être vérifiée explicitement auprès du fournisseur concerné avant intégration.
|
||||||
|
|
||||||
|
Tauri n'est donc pas une stratégie de contournement pour utiliser une régie Web dans un binaire Desktop.
|
||||||
|
|
||||||
## Publicités récompensées
|
## Publicités récompensées
|
||||||
|
|
||||||
Usages possibles : seconde chance, revive, bonus x2, coffre gratuit, accélération, skin temporaire, monnaie supplémentaire, nouveau tirage ou poursuite d'une série.
|
Usages possibles : seconde chance, revive, bonus x2, coffre gratuit, accélération, skin temporaire, monnaie supplémentaire, nouveau tirage ou poursuite d'une série.
|
||||||
|
|
||||||
La récompense n'est accordée qu'après réception de l'événement plateforme indiquant que la condition publicitaire requise est satisfaite.
|
La logique de jeu doit prévoir un chemin normal lorsque la publicité récompensée est indisponible. Une récompense publicitaire n'est accordée qu'après réception de l'événement de succès défini par le backend actif.
|
||||||
|
|
||||||
## Interstitiels
|
## Interstitiels
|
||||||
|
|
||||||
Les emplacements naturels sont entre deux parties, après plusieurs manches, à la fin d'un niveau ou après un écran de résultats. Ils ne doivent pas interrompre arbitrairement une action en cours.
|
Les emplacements naturels sont entre deux parties, après plusieurs manches, à la fin d'un niveau ou après un écran de résultats. Ils ne doivent pas interrompre arbitrairement une action en cours.
|
||||||
|
|
||||||
## Achat de suppression des publicités
|
## Suppression des publicités
|
||||||
|
|
||||||
Une option `remove ads` peut supprimer les publicités non récompensées tout en laissant éventuellement disponibles les rewarded ads volontaires.
|
Une option `remove ads` peut supprimer les publicités non récompensées tout en laissant éventuellement disponibles les rewarded ads volontaires. Cette politique appartient à la distribution et au jeu, pas au moteur commun.
|
||||||
|
|
||||||
## Autres achats
|
## Billing et achats
|
||||||
|
|
||||||
Des achats ou déblocages peuvent concerner skins, personnages, cosmétiques ou packs. Les achats numériques doivent respecter les règles de facturation de la plateforme de distribution utilisée.
|
Des achats ou déblocages peuvent concerner skins, personnages, cosmétiques ou packs. Le backend de billing est lui aussi optionnel et spécifique à la plateforme de distribution.
|
||||||
|
|
||||||
## Médiation
|
## Médiation
|
||||||
|
|
||||||
Une plateforme de médiation peut agréger plusieurs demandes publicitaires. Cette architecture ne doit pas remonter dans le gameplay : le moteur manipule placements, disponibilité, affichage, fermeture, récompense et erreurs, pas le réseau publicitaire gagnant.
|
Une plateforme de médiation peut agréger plusieurs demandes publicitaires. Cette architecture ne remonte pas dans le gameplay : le moteur manipule disponibilité, placement, affichage, fermeture, récompense et erreurs, pas le réseau publicitaire gagnant.
|
||||||
|
|
||||||
## Configuration distante
|
## Configuration distante
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/rules/RULES_COMMANDS.md -->
|
<!-- file: docs/rules/RULES_COMMANDS.md -->
|
||||||
<!-- version: 1 -->
|
<!-- version: 2 -->
|
||||||
|
|
||||||
# Règles d'exécution des commandes
|
# Règles d'exécution des commandes
|
||||||
|
|
||||||
@@ -11,6 +11,10 @@
|
|||||||
- **CMD-GEN-004** — Une gate n'est déclarée réussie que si la commande exacte a été exécutée sur l'état livré.
|
- **CMD-GEN-004** — Une gate n'est déclarée réussie que si la commande exacte a été exécutée sur l'état livré.
|
||||||
- **CMD-GEN-005** — Les commandes ciblées sont préférées pendant le développement ; les commandes workspace complètes sont utilisées aux gates de livraison.
|
- **CMD-GEN-005** — Les commandes ciblées sont préférées pendant le développement ; les commandes workspace complètes sont utilisées aux gates de livraison.
|
||||||
- **CMD-GEN-006** — Les scripts `audit_*` sont des contrôles en lecture seule et ne corrigent jamais automatiquement les fichiers.
|
- **CMD-GEN-006** — Les scripts `audit_*` sont des contrôles en lecture seule et ne corrigent jamais automatiquement les fichiers.
|
||||||
|
- **CMD-GEN-007** — Sauf demande explicite contraire, les commandes Cargo de validation finale sont exécutées par l'utilisateur sur son workspace local et non déclarées réussies par le générateur.
|
||||||
|
- **CMD-GEN-008** — Chaque fichier delta indique les commandes de validation que l'utilisateur doit exécuter et le statut connu de la validation du delta précédent.
|
||||||
|
- **CMD-GEN-009** — Lorsque l'utilisateur fournit une gate complète propre, le delta est considéré validé et le travail peut passer automatiquement au delta planifié suivant sauf instruction contraire.
|
||||||
|
- **CMD-GEN-010** — Si une gate échoue, la progression vers le delta suivant est suspendue ; le correctif est livré sous le suffixe `.fix.N` du delta courant, sauf décision explicite contraire.
|
||||||
|
|
||||||
## Rust et Cargo
|
## Rust et Cargo
|
||||||
|
|
||||||
@@ -32,6 +36,8 @@
|
|||||||
- **CMD-DESKTOP-002** — Le runner Desktop dépend de la crate lib du jeu et ne duplique pas le gameplay.
|
- **CMD-DESKTOP-002** — Le runner Desktop dépend de la crate lib du jeu et ne duplique pas le gameplay.
|
||||||
- **CMD-DESKTOP-003** — Le runner Desktop est la voie privilégiée pour les itérations fonctionnelles rapides qui ne nécessitent pas une capacité Android spécifique.
|
- **CMD-DESKTOP-003** — Le runner Desktop est la voie privilégiée pour les itérations fonctionnelles rapides qui ne nécessitent pas une capacité Android spécifique.
|
||||||
- **CMD-DESKTOP-004** — Un test Android reste obligatoire pour toute fonctionnalité dépendant du lifecycle, du tactile réel, de JNI, d'Ads, de Billing, de haptique ou d'une API Android.
|
- **CMD-DESKTOP-004** — Un test Android reste obligatoire pour toute fonctionnalité dépendant du lifecycle, du tactile réel, de JNI, d'Ads, de Billing, de haptique ou d'une API Android.
|
||||||
|
- **CMD-DESKTOP-005** — Le runner Desktop natif SDL3 est la distribution Desktop de jeu par défaut. Une variante Tauri est optionnelle, distincte et n'est créée que pour un besoin explicite.
|
||||||
|
- **CMD-DESKTOP-006** — Une variante Tauri dépend de la même crate lib de jeu et ne duplique jamais le gameplay du runner natif.
|
||||||
|
|
||||||
## Android et Gradle
|
## Android et Gradle
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/rules/RULES_PROJECT.md -->
|
<!-- file: docs/rules/RULES_PROJECT.md -->
|
||||||
<!-- version: 2 -->
|
<!-- version: 3 -->
|
||||||
|
|
||||||
# Règles spécifiques games.sasedev
|
# Règles spécifiques games.sasedev
|
||||||
|
|
||||||
@@ -46,6 +46,9 @@
|
|||||||
- **GAME-PLATFORM-001** — Le gameplay ne dépend pas directement d'Android, Desktop ou Web.
|
- **GAME-PLATFORM-001** — Le gameplay ne dépend pas directement d'Android, Desktop ou Web.
|
||||||
- **GAME-PLATFORM-002** — Les entrées physiques sont traduites en actions de jeu abstraites.
|
- **GAME-PLATFORM-002** — Les entrées physiques sont traduites en actions de jeu abstraites.
|
||||||
- **GAME-PLATFORM-003** — Les services Ads, Billing, Share, Haptics, Leaderboard et stockage en ligne sont consommés derrière des contrats plateforme.
|
- **GAME-PLATFORM-003** — Les services Ads, Billing, Share, Haptics, Leaderboard et stockage en ligne sont consommés derrière des contrats plateforme.
|
||||||
|
- **GAME-PLATFORM-004** — Ads et Billing sont des capacités optionnelles ; le gameplay reste fonctionnel lorsqu'elles sont disponibles, désactivées ou non supportées.
|
||||||
|
- **GAME-PLATFORM-005** — Un backend de monétisation est spécifique à sa plateforme et à sa distribution ; une intégration Android n'est pas réutilisée implicitement sur Web ou Desktop.
|
||||||
|
- **GAME-PLATFORM-006** — Le runner Desktop natif SDL3 est la forme Desktop par défaut. Une variante Tauri peut coexister uniquement lorsqu'un besoin explicite le justifie et consomme la même crate lib de jeu.
|
||||||
|
|
||||||
## POC
|
## POC
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
<!-- file: docs/validation/001-VALIDATION_GATES.md -->
|
<!-- file: docs/validation/001-VALIDATION_GATES.md -->
|
||||||
<!-- version: 2 -->
|
<!-- version: 3 -->
|
||||||
|
|
||||||
# Gates de validation
|
# Gates de validation
|
||||||
|
|
||||||
Gate Rust et documentation canonique :
|
## Responsabilité
|
||||||
|
|
||||||
|
Les audits Python peuvent être exécutés pendant la préparation d'un delta. Sauf demande explicite contraire, les commandes Cargo finales sont exécutées par l'utilisateur sur le workspace local.
|
||||||
|
|
||||||
|
Le générateur ne marque jamais une commande Cargo comme propre s'il ne dispose que du résultat d'un delta antérieur.
|
||||||
|
|
||||||
|
## Gate canonique Rust et documentation
|
||||||
|
|
||||||
|
À exécuter par l'utilisateur pour valider chaque delta contenant du code, du build, des manifests ou des règles affectant le workspace :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo fmt --all -- --check
|
cargo fmt --all -- --check
|
||||||
@@ -14,13 +22,25 @@ cargo clippy --workspace --all-targets --all-features -- -D warnings
|
|||||||
cargo test --workspace --all-targets --all-features
|
cargo test --workspace --all-targets --all-features
|
||||||
```
|
```
|
||||||
|
|
||||||
Smokes Desktop ciblés lorsque le comportement exécutable concerné doit être observé :
|
## Smokes Desktop
|
||||||
|
|
||||||
|
Lorsqu'un delta modifie la boucle exécutable, le runtime ou le comportement visible des runners Desktop, exécuter également :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo run -p game-reflex-poc-desktop
|
cargo run -p game-reflex-poc-desktop
|
||||||
cargo run -p game-snake-poc-desktop
|
cargo run -p game-snake-poc-desktop
|
||||||
```
|
```
|
||||||
|
|
||||||
Les smokes `cargo run` ne remplacent pas la gate de tests et ne sont pas obligatoires pour une modification purement documentaire.
|
Les smokes `cargo run` ne remplacent pas la gate de tests.
|
||||||
|
|
||||||
Le build Android n'est pas encore une gate : le projet Gradle exécutable et l'intégration SDL3/NDK restent planifiés pour une prerelease dédiée. Dès leur introduction, les tâches ciblées par module seront documentées avant d'être rendues obligatoires.
|
## Transition vers le delta suivant
|
||||||
|
|
||||||
|
Si toutes les commandes demandées sont propres, le delta courant est validé et le travail peut passer automatiquement au prochain delta de la roadmap, sauf avis contraire de l'utilisateur.
|
||||||
|
|
||||||
|
Si une commande échoue, ne pas avancer de prerelease. Produire un correctif du delta courant, par exemple `0.1.0-0-pre.3.fix.1`, puis rejouer la gate demandée.
|
||||||
|
|
||||||
|
## Android et Web
|
||||||
|
|
||||||
|
Le build Android n'est pas encore une gate : le projet Gradle exécutable et l'intégration SDL3/NDK restent planifiés pour une prerelease dédiée. Dès leur introduction, les tâches ciblées par module sont documentées avant d'être rendues obligatoires.
|
||||||
|
|
||||||
|
La cible Web suit la même règle : aucune gate Web n'est inventée avant l'existence de son build réel.
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
<!-- file: prompts/000-V0_1_0_START_PROMPT.md -->
|
<!-- file: prompts/000-V0_1_0_START_PROMPT.md -->
|
||||||
<!-- version: 2 -->
|
<!-- version: 3 -->
|
||||||
|
|
||||||
# Prompt de reprise 0.1.0
|
# Prompt de reprise 0.1.0
|
||||||
|
|
||||||
Partir de la dernière livraison `0.1.0-*`, lire `RULES.md`, `ROADMAP.md`, `CHANGELOG.md`, le dernier delta et les documents d'architecture concernés. Vérifier l'état réel du workspace avant toute modification. Respecter le format de livraison delta, le cycle SemVer défini dans `docs/rules/VERSION_WORKFLOW.md` et la politique d’exécution de `docs/rules/RULES_COMMANDS.md`. Pour une évolution de gameplay portable, privilégier le runner Desktop du jeu avant Android lorsque la fonctionnalité ne dépend pas d’une API Android.
|
Partir de la dernière livraison `0.1.0-*`, lire `RULES.md`, `ROADMAP.md`, `CHANGELOG.md`, le dernier delta et les documents d'architecture concernés. Vérifier l'état réel du workspace avant toute modification. Respecter le format de livraison delta, le cycle SemVer défini dans `docs/rules/VERSION_WORKFLOW.md` et la politique d’exécution de `docs/rules/RULES_COMMANDS.md`. Pour une évolution de gameplay portable, privilégier le runner Desktop du jeu avant Android lorsque la fonctionnalité ne dépend pas d’une API Android.
|
||||||
|
|
||||||
|
## Validation et enchaînement
|
||||||
|
|
||||||
|
La validation Cargo finale d'un delta est exécutée par l'utilisateur sauf demande explicite contraire. Le delta suivant doit reprendre le statut de cette validation. Une gate entièrement propre autorise le passage automatique au delta suivant ; une gate en échec impose un `.fix.N` du delta courant avant progression, sauf décision explicite contraire.
|
||||||
|
|||||||
Reference in New Issue
Block a user