Prepare tester feedback and reproducible alpha builds
This commit is contained in:
1 parent
cad08aafb7
commit
235d7f3810
7 files changed
+120
-4
No files matched your search
@@ -0,0 +1,46 @@
|
|||||||
|
# Void Bastion – Alpha-Testcheckliste
|
||||||
|
|
||||||
|
Diese Liste wird vor jeder öffentlichen Windows-Alpha vollständig abgearbeitet. Automatische Prüfungen sind mit **A**, manuelle Sicht- und Bedienprüfungen mit **M** gekennzeichnet.
|
||||||
|
|
||||||
|
## Build und Installation
|
||||||
|
|
||||||
|
- [ ] **A** `tests/smoke_test.gd` läuft ohne Fehler.
|
||||||
|
- [ ] **A** `tests/online_integration_test.gd` läuft gegen `api.void-bastion.gergames.de` ohne Fehler.
|
||||||
|
- [ ] **A** Windows-Export und Setup werden mit `packaging/windows/build.ps1` reproduzierbar erstellt.
|
||||||
|
- [ ] **A** Entwicklerwerkzeuge, Tests, Serverdateien und interne MCP-Konfigurationen sind nicht im Windows-Paket enthalten.
|
||||||
|
- [ ] **M** Setup installiert ohne Administratorrechte, Startmenü- und Desktop-Verknüpfung funktionieren.
|
||||||
|
- [ ] **M** Deinstallation entfernt Programmdateien und Verknüpfungen vollständig.
|
||||||
|
- [ ] **M** Windows-SmartScreen-Hinweis ist in den Testhinweisen genannt, solange der Installer noch nicht signiert ist.
|
||||||
|
|
||||||
|
## Darstellung und Bedienung
|
||||||
|
|
||||||
|
- [ ] **M** 1920 × 1080, 2560 × 1440 und eine kleinere 16:9-Auflösung prüfen: Karte und UI bleiben scharf und vollständig sichtbar.
|
||||||
|
- [ ] **M** Alle drei Karten prüfen: Straße, Gegnerlaufweg, Raster und Bausperren stimmen exakt überein.
|
||||||
|
- [ ] **M** Reaktoren und freie Weltraumbereiche sind auf allen Karten unbebaubar.
|
||||||
|
- [ ] **M** Reichweitenvorschau erscheint vor dem Bau; Turmwerte und Upgrade-Mehrwert sind lesbar.
|
||||||
|
- [ ] **M** Zurück-Schaltfläche bricht eine laufende Runde verständlich ab.
|
||||||
|
|
||||||
|
## Spielablauf
|
||||||
|
|
||||||
|
- [ ] **M** Jede Karte einmal auf Leicht, Mittel und Schwer starten; Startressourcen und Gegnerdruck unterscheiden sich sichtbar.
|
||||||
|
- [ ] **M** Eine Mission bis Welle 15 spielen: Sieg kann beendet oder freiwillig als Endloslauf fortgesetzt werden.
|
||||||
|
- [ ] **M** Eine Niederlage im Endlosmodus wird nicht als gescheiterte Mission gewertet.
|
||||||
|
- [ ] **M** Kernkristall-Belohnung wird nach erfolgreicher Karte gespeichert.
|
||||||
|
- [ ] **M** Spielstand, Einstellungen und Bestwerte bleiben nach einem Neustart erhalten.
|
||||||
|
|
||||||
|
## Konto und Onlinefunktionen
|
||||||
|
|
||||||
|
- [ ] **M** Gastkonto wird beim ersten Start erzeugt; Spielername lässt sich ändern.
|
||||||
|
- [ ] **M** Profil zeigt Version, gekürzte Spieler-ID, Datenschutz und Support/Feedback.
|
||||||
|
- [ ] **M** Bestenliste lädt; ein neuer Bestwert wird korrekt übertragen und dem sichtbaren Spielernamen zugeordnet.
|
||||||
|
- [ ] **M** Kontolöschung verlangt eine zweite Bestätigung und erzeugt anschließend ein neues Gastprofil.
|
||||||
|
- [ ] **M** Privater Raum: Erstellen, Code eingeben, Bereit-Lobby, Partiestart und Abbruch mit zwei Geräten prüfen.
|
||||||
|
- [ ] **M** Kurzen Netzabbruch während einer Onlinepartie und die Wiederverbindung prüfen.
|
||||||
|
- [ ] **M** Lokaler Koop mit KI: getrennte Ressourcen, zwei Gegnerwege, Seitenwechsel und gemeinsames Reaktorleben prüfen.
|
||||||
|
|
||||||
|
## Veröffentlichung
|
||||||
|
|
||||||
|
- [ ] **M** Setup-Datei und SHA-256-Prüfsumme stimmen mit der öffentlichen Release-Datei überein.
|
||||||
|
- [ ] **M** Download-Schaltfläche auf `https://void-bastion.gergames.de/` lädt die aktuelle Setup-Datei.
|
||||||
|
- [ ] **M** Datenschutz, Impressum, Supportadresse und Kontolöschseite sind öffentlich erreichbar.
|
||||||
|
- [ ] **M** Änderungen, bekannte Fehler und Testhinweise stehen in den Release-Notizen.
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Void Bastion – spielbarer Prototyp 0.23
|
# Void Bastion - Alpha 0.1.0
|
||||||
|
|
||||||
Ein plattformübergreifender 2D-Tower-Defense-Prototyp für Godot 4. Der aktuelle Stand ist für Windows, Android und später iOS ausgelegt.
|
Ein plattformübergreifender 2D-Tower-Defense-Prototyp für Godot 4. Der aktuelle Stand ist für Windows, Android und später iOS ausgelegt.
|
||||||
|
|
||||||
@@ -41,6 +41,7 @@ Ein plattformübergreifender 2D-Tower-Defense-Prototyp für Godot 4. Der aktuell
|
|||||||
- frei wählbarer Spielername mit identischer Prüfung im Spiel und auf dem Server
|
- frei wählbarer Spielername mit identischer Prüfung im Spiel und auf dem Server
|
||||||
- zweistufige Kontolöschung direkt im Pilotenprofil; danach wird ein neues lokales Gastprofil erstellt
|
- zweistufige Kontolöschung direkt im Pilotenprofil; danach wird ein neues lokales Gastprofil erstellt
|
||||||
- direkter Datenschutz-Link im Pilotenprofil
|
- direkter Datenschutz-Link im Pilotenprofil
|
||||||
|
- sichtbare App-Version und vorbereitete Support-Mail an `support@gergames.de` im Pilotenprofil; Version, Plattform und gekürzte Spieler-ID werden für eine schnellere Fehlerzuordnung eingesetzt
|
||||||
- wartende, lokal gespeicherte Highscore-Einträge für die spätere sichere Serverübertragung
|
- wartende, lokal gespeicherte Highscore-Einträge für die spätere sichere Serverübertragung
|
||||||
- eigener Multiplayerbereich mit schneller Spielersuche sowie privaten Koop-Räumen mit sechsstelligen Raumcodes
|
- eigener Multiplayerbereich mit schneller Spielersuche sowie privaten Koop-Räumen mit sechsstelligen Raumcodes
|
||||||
- gemeinsame Online-Lobby mit Spielernamen, festen Verteidigungsseiten und manuellem Bereit-Status für beide Piloten
|
- gemeinsame Online-Lobby mit Spielernamen, festen Verteidigungsseiten und manuellem Bereit-Status für beide Piloten
|
||||||
@@ -81,6 +82,8 @@ Der Prototyp wurde mit der offiziellen Godot-Version 4.7.2 importiert und im aut
|
|||||||
|
|
||||||
Der mitgelieferte Logiktest lässt sich bei Bedarf mit `godot --headless --path . --script res://tests/smoke_test.gd` erneut ausführen.
|
Der mitgelieferte Logiktest lässt sich bei Bedarf mit `godot --headless --path . --script res://tests/smoke_test.gd` erneut ausführen.
|
||||||
|
|
||||||
|
Für reproduzierbare Windows-Builds steht `packaging/windows/build.ps1` bereit. Godot und NSIS werden als Parameter übergeben; das Skript exportiert das Spiel, erstellt den Installer und gibt anschließend Größe und SHA-256-Prüfsumme aus. Die manuellen Freigabeprüfungen stehen in `ALPHA_TEST_CHECKLIST.md`.
|
||||||
|
|
||||||
## Werbung und Datenschutz
|
## Werbung und Datenschutz
|
||||||
|
|
||||||
Die mobile Werbeanbindung ist technisch vorbereitet, aber absichtlich noch nicht für echte Werbung freigeschaltet:
|
Die mobile Werbeanbindung ist technisch vorbereitet, aber absichtlich noch nicht für echte Werbung freigeschaltet:
|
||||||
@@ -107,7 +110,7 @@ Die öffentliche Spiel-API ist unter `api.void-bastion.gergames.de` erreichbar.
|
|||||||
|
|
||||||
## Steuerung
|
## Steuerung
|
||||||
|
|
||||||
> **Online-Stand 0.23:** Der offizielle Nakama-Godot-Client, automatische Gastanmeldung, servergeprüfte öffentliche Spielernamen, Kontolöschung im Spiel, 36 autoritative Ranglisten, geprüfte Ergebnisübermittlung, Echtzeit-Sockets, serverautoritativer Zwei-Spieler-Koop, private Räume per Raumcode, eine Bereit-Lobby und Wiederverbindung sind eingebaut. Ranglisten lösen den aktuellen öffentlichen Profilnamen über die unveränderliche Spieler-ID auf. Die öffentliche API ist über `https://api.void-bastion.gergames.de` mit einem gültigen Let's-Encrypt-Zertifikat freigeschaltet. Details stehen in `ONLINE_SETUP.md`.
|
> **Online-Stand Alpha 0.1.0:** Der offizielle Nakama-Godot-Client, automatische Gastanmeldung, servergeprüfte öffentliche Spielernamen, Kontolöschung im Spiel, 36 autoritative Ranglisten, geprüfte Ergebnisübermittlung, Echtzeit-Sockets, serverautoritativer Zwei-Spieler-Koop, private Räume per Raumcode, eine Bereit-Lobby und Wiederverbindung sind eingebaut. Ranglisten lösen den aktuellen öffentlichen Profilnamen über die unveränderliche Spieler-ID auf. Die öffentliche API ist über `https://api.void-bastion.gergames.de` mit einem gültigen Let's-Encrypt-Zertifikat freigeschaltet. Details stehen in `ONLINE_SETUP.md`.
|
||||||
|
|
||||||
Die Schaltfläche **Bestenliste** im Hauptmenü zeigt die zwölf besten Piloten für die aktuell gewählte Karte und Schwierigkeit. Zwischen Mission und Endlosmodus kann direkt in der Ansicht umgeschaltet werden; der eigene Eintrag wird farblich hervorgehoben.
|
Die Schaltfläche **Bestenliste** im Hauptmenü zeigt die zwölf besten Piloten für die aktuell gewählte Karte und Schwierigkeit. Zwischen Mission und Endlosmodus kann direkt in der Ansicht umgeschaltet werden; der eigene Eintrag wird farblich hervorgehoben.
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ dedicated_server=false
|
|||||||
custom_features=""
|
custom_features=""
|
||||||
export_filter="all_resources"
|
export_filter="all_resources"
|
||||||
include_filter=""
|
include_filter=""
|
||||||
exclude_filter=""
|
exclude_filter=".mcp.json,*.md,tests/*,server/*,tools/*,packaging/*,addons/agent_tools/*,build/*,dist/*"
|
||||||
export_path="build/windows/Void-Bastion.exe"
|
export_path="build/windows/Void-Bastion.exe"
|
||||||
patches=PackedStringArray()
|
patches=PackedStringArray()
|
||||||
|
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ var profile_name_input := ""
|
|||||||
var profile_message := ""
|
var profile_message := ""
|
||||||
var profile_delete_armed := false
|
var profile_delete_armed := false
|
||||||
var profile_rename_button := Rect2(220, 305, 250, 48)
|
var profile_rename_button := Rect2(220, 305, 250, 48)
|
||||||
|
var profile_support_button := Rect2(570, 685, 250, 48)
|
||||||
var profile_privacy_button := Rect2(840, 685, 250, 48)
|
var profile_privacy_button := Rect2(840, 685, 250, 48)
|
||||||
var profile_delete_button := Rect2(1110, 685, 250, 48)
|
var profile_delete_button := Rect2(1110, 685, 250, 48)
|
||||||
var profile_name_input_rect := Rect2(460, 330, 680, 82)
|
var profile_name_input_rect := Rect2(460, 330, 680, 82)
|
||||||
@@ -505,6 +506,8 @@ func _handle_pointer(position: Vector2, is_touch := false) -> void:
|
|||||||
game_mode = "menu"
|
game_mode = "menu"
|
||||||
elif profile_rename_button.has_point(position):
|
elif profile_rename_button.has_point(position):
|
||||||
_open_profile_name_entry()
|
_open_profile_name_entry()
|
||||||
|
elif profile_support_button.has_point(position):
|
||||||
|
OS.shell_open(_support_email_url())
|
||||||
elif profile_privacy_button.has_point(position):
|
elif profile_privacy_button.has_point(position):
|
||||||
OS.shell_open("https://void-bastion.gergames.de/privacy.html")
|
OS.shell_open("https://void-bastion.gergames.de/privacy.html")
|
||||||
elif profile_delete_button.has_point(position):
|
elif profile_delete_button.has_point(position):
|
||||||
@@ -742,6 +745,20 @@ func _hide_virtual_keyboard() -> void:
|
|||||||
DisplayServer.virtual_keyboard_hide()
|
DisplayServer.virtual_keyboard_hide()
|
||||||
|
|
||||||
|
|
||||||
|
func _app_version() -> String:
|
||||||
|
return str(ProjectSettings.get_setting("application/config/version", "Entwicklung"))
|
||||||
|
|
||||||
|
|
||||||
|
func _support_email_url() -> String:
|
||||||
|
var subject := "Void Bastion Feedback %s" % _app_version()
|
||||||
|
var body := "Version: %s\nPlattform: %s\nSpieler-ID: %s\n\nBeschreibung:\n" % [
|
||||||
|
_app_version(),
|
||||||
|
OS.get_name(),
|
||||||
|
account_manager.short_id(),
|
||||||
|
]
|
||||||
|
return "mailto:support@gergames.de?subject=%s&body=%s" % [subject.uri_encode(), body.uri_encode()]
|
||||||
|
|
||||||
|
|
||||||
func _open_profile_name_entry() -> void:
|
func _open_profile_name_entry() -> void:
|
||||||
profile_name_input = account_manager.display_name
|
profile_name_input = account_manager.display_name
|
||||||
profile_message = ""
|
profile_message = ""
|
||||||
@@ -1796,6 +1813,7 @@ func _draw_profile() -> void:
|
|||||||
draw_rect(Rect2(Vector2.ZERO, SCREEN), Color(0.005, 0.015, 0.035, 0.76))
|
draw_rect(Rect2(Vector2.ZERO, SCREEN), Color(0.005, 0.015, 0.035, 0.76))
|
||||||
_draw_button(profile_back_button, "< ZURÜCK", false, Color("26394a"))
|
_draw_button(profile_back_button, "< ZURÜCK", false, Color("26394a"))
|
||||||
draw_string(font, Vector2(0, 112), "PILOTENPROFIL", HORIZONTAL_ALIGNMENT_CENTER, 1600, 44, Color("c7f5ff"))
|
draw_string(font, Vector2(0, 112), "PILOTENPROFIL", HORIZONTAL_ALIGNMENT_CENTER, 1600, 44, Color("c7f5ff"))
|
||||||
|
draw_string(font, Vector2(1110, 112), "VERSION %s" % _app_version(), HORIZONTAL_ALIGNMENT_RIGHT, 290, 14, Color("91a8bc"))
|
||||||
draw_rect(Rect2(170, 155, 1260, 600), Color(0.012, 0.035, 0.065, 0.94), true)
|
draw_rect(Rect2(170, 155, 1260, 600), Color(0.012, 0.035, 0.065, 0.94), true)
|
||||||
draw_rect(Rect2(170, 155, 1260, 600), Color("4ab8d1"), false, 2.0)
|
draw_rect(Rect2(170, 155, 1260, 600), Color("4ab8d1"), false, 2.0)
|
||||||
draw_string(font, Vector2(220, 220), account_manager.display_name, HORIZONTAL_ALIGNMENT_LEFT, 520, 30, Color("8ff4ff"))
|
draw_string(font, Vector2(220, 220), account_manager.display_name, HORIZONTAL_ALIGNMENT_LEFT, 520, 30, Color("8ff4ff"))
|
||||||
@@ -1824,6 +1842,7 @@ func _draw_profile() -> void:
|
|||||||
draw_string(font, Vector2(1160, y), "%07d" % score, HORIZONTAL_ALIGNMENT_CENTER, 180, 16, Color("8fffea"))
|
draw_string(font, Vector2(1160, y), "%07d" % score, HORIZONTAL_ALIGNMENT_CENTER, 180, 16, Color("8fffea"))
|
||||||
if not profile_message.is_empty():
|
if not profile_message.is_empty():
|
||||||
draw_string(font, Vector2(220, 658), profile_message, HORIZONTAL_ALIGNMENT_CENTER, 1160, 14, Color("ffcf7a"))
|
draw_string(font, Vector2(220, 658), profile_message, HORIZONTAL_ALIGNMENT_CENTER, 1160, 14, Color("ffcf7a"))
|
||||||
|
_draw_button(profile_support_button, "SUPPORT / FEEDBACK", false, Color("24516a"))
|
||||||
_draw_button(profile_privacy_button, "DATENSCHUTZ", false, Color("24475b"))
|
_draw_button(profile_privacy_button, "DATENSCHUTZ", false, Color("24475b"))
|
||||||
_draw_button(profile_delete_button, "LÖSCHEN BESTÄTIGEN" if profile_delete_armed else "KONTO LÖSCHEN", profile_delete_armed, Color("6b2d3b"))
|
_draw_button(profile_delete_button, "LÖSCHEN BESTÄTIGEN" if profile_delete_armed else "KONTO LÖSCHEN", profile_delete_armed, Color("6b2d3b"))
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -129,7 +129,7 @@ func submit_run(record: Dictionary) -> bool:
|
|||||||
if not is_authenticated():
|
if not is_authenticated():
|
||||||
return false
|
return false
|
||||||
var payload := record.duplicate(true)
|
var payload := record.duplicate(true)
|
||||||
payload["client_version"] = "0.23"
|
payload["client_version"] = str(ProjectSettings.get_setting("application/config/version", "development"))
|
||||||
var result = await client.rpc_async(session, "submit_run", JSON.stringify(payload))
|
var result = await client.rpc_async(session, "submit_run", JSON.stringify(payload))
|
||||||
var parsed := _parse_rpc_result(result)
|
var parsed := _parse_rpc_result(result)
|
||||||
if parsed.is_empty() or not bool(parsed.get("accepted", false)):
|
if parsed.is_empty() or not bool(parsed.get("accepted", false)):
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
param(
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$GodotExe,
|
||||||
|
|
||||||
|
[Parameter(Mandatory = $true)]
|
||||||
|
[string]$MakeNsisExe
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
$projectRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).Path
|
||||||
|
$gameExe = Join-Path $projectRoot "build\windows\Void-Bastion.exe"
|
||||||
|
$installer = Join-Path $projectRoot "dist\Void-Bastion-Setup-x64.exe"
|
||||||
|
$installerScript = Join-Path $PSScriptRoot "installer.nsi"
|
||||||
|
|
||||||
|
foreach ($requiredFile in @($GodotExe, $MakeNsisExe, $installerScript)) {
|
||||||
|
if (-not (Test-Path -LiteralPath $requiredFile -PathType Leaf)) {
|
||||||
|
throw "Benötigte Datei fehlt: $requiredFile"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
New-Item -ItemType Directory -Force -Path (Split-Path $gameExe), (Split-Path $installer) | Out-Null
|
||||||
|
|
||||||
|
& $GodotExe --headless --path $projectRoot --export-release "Windows x64" $gameExe
|
||||||
|
if ($LASTEXITCODE -ne 0 -or -not (Test-Path -LiteralPath $gameExe)) {
|
||||||
|
throw "Der Godot-Windows-Export ist fehlgeschlagen."
|
||||||
|
}
|
||||||
|
|
||||||
|
Push-Location $PSScriptRoot
|
||||||
|
try {
|
||||||
|
& $MakeNsisExe $installerScript
|
||||||
|
if ($LASTEXITCODE -ne 0 -or -not (Test-Path -LiteralPath $installer)) {
|
||||||
|
throw "Die Installer-Erstellung mit NSIS ist fehlgeschlagen."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
Pop-Location
|
||||||
|
}
|
||||||
|
|
||||||
|
$installerFile = Get-Item -LiteralPath $installer
|
||||||
|
$hash = Get-FileHash -Algorithm SHA256 -LiteralPath $installer
|
||||||
|
Write-Host "Windows-Installer erfolgreich erstellt:"
|
||||||
|
Write-Host " Datei: $($installerFile.FullName)"
|
||||||
|
Write-Host " Größe: $([math]::Round($installerFile.Length / 1MB, 1)) MiB"
|
||||||
|
Write-Host " SHA256: $($hash.Hash)"
|
||||||
@@ -82,6 +82,10 @@ func _run() -> void:
|
|||||||
assert(game.get("game_mode") == "menu", "Die Bestenliste benötigt einen funktionierenden Zurück-Button.")
|
assert(game.get("game_mode") == "menu", "Die Bestenliste benötigt einen funktionierenden Zurück-Button.")
|
||||||
assert(not account_manager.player_id.is_empty(), "Beim ersten Start muss automatisch ein stabiles Gastprofil angelegt werden.")
|
assert(not account_manager.player_id.is_empty(), "Beim ersten Start muss automatisch ein stabiles Gastprofil angelegt werden.")
|
||||||
assert(not account_manager.display_name.is_empty(), "Das Gastprofil benötigt einen sichtbaren Pilotennamen.")
|
assert(not account_manager.display_name.is_empty(), "Das Gastprofil benötigt einen sichtbaren Pilotennamen.")
|
||||||
|
assert(game.call("_app_version") == "0.1.0-alpha", "Profil, Installer und Online-Übertragung müssen dieselbe Alpha-Version verwenden.")
|
||||||
|
var support_url: String = game.call("_support_email_url")
|
||||||
|
assert(support_url.begins_with("mailto:support@gergames.de?"), "Die Feedback-Schaltfläche muss die öffentliche Supportadresse verwenden.")
|
||||||
|
assert(support_url.contains("0.1.0-alpha") and support_url.contains(account_manager.short_id()), "Die Support-Mail muss Version und gekürzte Spieler-ID zur Fehlerzuordnung enthalten.")
|
||||||
account_manager.storage_enabled = false
|
account_manager.storage_enabled = false
|
||||||
var original_display_name: String = account_manager.display_name
|
var original_display_name: String = account_manager.display_name
|
||||||
assert(account_manager.validate_display_name("Kommandant Ägir").is_empty(), "Ein normaler internationaler Spielername muss gültig sein.")
|
assert(account_manager.validate_display_name("Kommandant Ägir").is_empty(), "Ein normaler internationaler Spielername muss gültig sein.")
|
||||||
|
|||||||
Reference in new issue
Block a user