Reorganize the codebase into role-based folders (snake_case)

Move the 40-odd root-level scripts and scenes into a tree that groups by role,
per the approved roadmap (WS2/WS3): actors/{player,matador,bear}, systems/{combat,
fx,input,debug,render,crowd,config}, ui/, props/ (was tscn_s/). Rename the shell
scene.tscn -> levels/game_shell.tscn and every PascalCase scene to snake_case
(Player.tscn -> player.tscn, MainMenu.tscn -> main_menu.tscn, etc.) so the
case-insensitive web/macOS filesystems can't alias two files together.

Done as a scripted move: each file travels with its .uid; every quoted res:// path
across scenes, scripts, .tres, and project.godot's 9 autoloads is rewritten; the
.godot uid/class cache is rebuilt from scratch. Scenes still resolve by uid, so the
rewrite is belt-and-suspenders. Lint globs (run_tests.sh + both CI workflows) now
walk the new dirs; an orphan animation_tree.gd.uid and 7 pre-existing trailing-
whitespace lints (barrel/torch, never covered by the old root-only glob) are cleaned.

Full suite green on both sides. Asset consolidation (Assets/Animations/fonts ->
art/) is deferred — moving imported .glb/.png/.ttf is safer done in the editor.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017KoYBYQgRyY1zDHXMsXY3V
This commit is contained in:
2026-09-06 00:04:41 +03:00
parent b839252fe0
commit 7b6a141767
141 changed files with 199 additions and 158 deletions
+105
View File
@@ -0,0 +1,105 @@
extends CanvasLayer
## "TAP TO PLAY" gate for the web/touch build (autoload `WebStartGate`). Browsers only allow
## requestFullscreen / screen.orientation.lock from inside a user gesture, so instead of
## piggybacking the first *gameplay* tap (which used to race the resize/orientation flip and
## strand the joystick — a dropped touchend left the stick stuck), we capture a dedicated tap
## on a full-screen panel BEFORE play. The tap drives Controls.request_fullscreen_landscape()
## and the gate dismisses, so all the fullscreen/orientation churn happens with no in-flight
## touch to lose.
##
## Re-armable, so the player is never stuck windowed: whenever we're NOT fullscreen (boot, or
## after they left via Esc / Android back / edge swipe) the gate re-appears and its tap goes
## back in — fullscreen otherwise persists across menu ↔ game, so tapping Play never lands you
## windowed. Where fullscreen is impossible (iPhone Safari has no requestFullscreen) it shows
## once and then stays out of the way instead of nagging. Only active on web touch; no-op else.
const _POLL: float = 0.4 # seconds between browser fullscreen-state polls
var _panel: ColorRect
var _active: bool = false # web + touch: the only case this gate does anything
var _fs_supported: bool = true
var _dismissed_unsupported: bool = false # iPhone: tapped once, don't show again
var _poll_accum: float = 0.0
func _ready() -> void:
layer = 127 # above HUD/touch UI, just below OrientationGuard (128) so "rotate" wins in portrait
process_mode = Node.PROCESS_MODE_ALWAYS
_active = OS.has_feature("web") and Controls.use_touch_ui()
if not _active:
return
_fs_supported = Controls.fullscreen_supported()
_panel = ColorRect.new()
_panel.color = Color(0.05, 0.03, 0.02, 0.94)
_panel.set_anchors_preset(Control.PRESET_FULL_RECT)
_panel.mouse_filter = Control.MOUSE_FILTER_STOP # swallow the tap from the game behind it
add_child(_panel)
var center := CenterContainer.new()
center.set_anchors_preset(Control.PRESET_FULL_RECT)
center.mouse_filter = Control.MOUSE_FILTER_IGNORE
_panel.add_child(center)
var box := VBoxContainer.new()
box.alignment = BoxContainer.ALIGNMENT_CENTER
box.add_theme_constant_override("separation", 16)
center.add_child(box)
var title := Label.new()
title.text = "TAP TO PLAY"
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
title.add_theme_font_size_override("font_size", 52)
title.add_theme_color_override("font_color", Color(1.0, 0.78, 0.22))
box.add_child(title)
var sub := Label.new()
sub.text = "Fullscreen, landscape"
sub.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
sub.add_theme_font_size_override("font_size", 22)
sub.add_theme_color_override("font_color", Color(0.85, 0.85, 0.85))
box.add_child(sub)
func _input(event: InputEvent) -> void:
if not _active or not _panel.visible:
return
var gesture := (event is InputEventScreenTouch and (event as InputEventScreenTouch).pressed) \
or (event is InputEventMouseButton and (event as InputEventMouseButton).pressed)
if gesture:
# The native DOM listener (controls_manager.gd) enters fullscreen synchronously on this
# same tap; this is the belt-and-suspenders explicit call.
Controls.request_fullscreen_landscape()
_panel.visible = false
if not _fs_supported:
_dismissed_unsupported = true # iPhone: don't reappear, there's nothing to enter
get_viewport().set_input_as_handled()
func _process(delta: float) -> void:
if not _active:
return
_poll_accum += delta
if _poll_accum < _POLL:
return
_poll_accum = 0.0
# Portrait: OrientationGuard owns the screen — stand down until they turn the device.
if _is_portrait():
_panel.visible = false
return
if not _fs_supported:
# Can't ever enter fullscreen (iPhone) — offer the tap once, then get out of the way.
_panel.visible = not _dismissed_unsupported
return
# Show the gate whenever we're not fullscreen, and arm so the next tap (re-)enters.
var fs := Controls.is_browser_fullscreen()
_panel.visible = not fs
if not fs:
Controls.arm_fullscreen()
func _is_portrait() -> bool:
var s := get_viewport().get_visible_rect().size
return s.y > s.x