Initial project sync

This commit is contained in:
wxm
2026-07-06 22:55:14 -07:00
commit 8cca00d0da
1066 changed files with 58585 additions and 0 deletions
+58
View File
@@ -0,0 +1,58 @@
class_name ActionPatternExporter
extends RefCounted
const ACTION_DIR := "res://resources/actions"
static func export_patterns(action_dir := ACTION_DIR) -> Dictionary:
var patterns := {}
_collect_patterns(action_dir, patterns)
return patterns
static func to_text(action_dir := ACTION_DIR) -> String:
var patterns := export_patterns(action_dir)
var keys := patterns.keys()
keys.sort()
var lines: Array[String] = []
for key: String in keys:
lines.append("%s -> %s" % [key, patterns[key]])
return "\n".join(lines)
static func _collect_patterns(action_dir: String, patterns: Dictionary) -> void:
var dir := DirAccess.open(action_dir)
if dir == null:
return
var subdirs := dir.get_directories()
subdirs.sort()
for subdir: String in subdirs:
_collect_patterns("%s/%s" % [action_dir, subdir], patterns)
var files := dir.get_files()
files.sort()
for file_name: String in files:
if not file_name.ends_with(".tres"):
continue
var action: Resource = load("%s/%s" % [action_dir, file_name])
if action == null:
continue
if _is_air_only_action(action):
continue
var key := _pattern_key(action.get("input_pattern"))
if key.is_empty():
continue
patterns[key] = StringName(str(action.get("id")))
static func _pattern_key(pattern: Variant) -> String:
if not pattern is Array:
return ""
var key := ""
for symbol: Variant in pattern:
key += str(symbol)
return key.replace(" ", "").to_upper()
static func _is_air_only_action(action: Resource) -> bool:
var states = action.get("allowed_ground_states")
return states is Array and states.size() == 1 and states[0] == &"Airborne"
+1
View File
@@ -0,0 +1 @@
uid://c22iirhmkldt1
+102
View File
@@ -0,0 +1,102 @@
class_name TimeAnchorChartTool
extends RefCounted
## Chart-side tooling for time anchors (design doc 3.4):
## generate explicit periodic anchors, validate anchor/mask configuration,
## and simulate the runtime periodic scheduler for a constant streak.
const ChartEventScript := preload("res://resources/chart_event.gd")
const VALID_TIME_PHASES: Array[StringName] = [&"past", &"future"]
const VALID_TIME_PHASE_MASKS: Array[StringName] = [&"", &"both", &"past", &"future"]
static func validate_chart(chart: Resource, beat_time := 0.5, bad_window := 0.2, judgement_scale := 1.0) -> Array[String]:
var errors: Array[String] = []
if chart == null:
errors.append("chart is null")
return errors
if bad_window * judgement_scale > beat_time * 0.5:
errors.append("bad window %.3f exceeds half a beat %.3f; overlapping anchor windows at the 1-beat tier" % [bad_window * judgement_scale, beat_time * 0.5])
var initial = chart.get("initial_time_phase")
if initial != null and not VALID_TIME_PHASES.has(StringName(str(initial))):
errors.append("initial_time_phase must be past/future, got %s" % str(initial))
var seen_anchor_beats: Dictionary = {}
for event: Resource in chart.call("all_events"):
var event_key := str(event.call("key"))
var mask = event.get("time_phase_mask")
if mask != null and not VALID_TIME_PHASE_MASKS.has(StringName(str(mask))):
errors.append("%s: invalid time_phase_mask %s" % [event_key, str(mask)])
var event_type := StringName(str(event.get("event_type")))
if event_type == &"time_anchor":
if int(event.get("subdivision")) != 0:
errors.append("%s: time_anchor must sit on an integer beat" % event_key)
var beat := int(event.get("beat_index"))
if seen_anchor_beats.has(beat):
errors.append("%s: duplicate time_anchor on beat %d" % [event_key, beat])
seen_anchor_beats[beat] = true
elif event_type == &"force_time_phase":
var payload = event.get("payload")
var target := StringName(str(payload.get("target_time_phase", ""))) if payload is Dictionary else &""
if not VALID_TIME_PHASES.has(target):
errors.append("%s: force_time_phase needs payload.target_time_phase past/future" % event_key)
return errors
static func validate_time_phase_profile(profile: Resource) -> Array[String]:
var errors: Array[String] = []
if profile == null:
return errors
for property_name: String in ["past_effects", "future_effects"]:
var definitions = profile.get(property_name)
if not definitions is Array:
continue
for definition: Variant in definitions:
if definition is Resource and StringName(str((definition as Resource).get("duration_type"))) != &"infinite":
errors.append("%s: effect %s must be duration_type infinite (lifecycle owned by TimePhaseAdapter)" % [property_name, str((definition as Resource).get("id"))])
return errors
static func generate_periodic_time_anchor_events(interval_beats: int, total_beats: int, lead_beats := 2.0) -> Array[Resource]:
var events: Array[Resource] = []
var safe_interval := maxi(1, interval_beats)
var beat := safe_interval
while beat < total_beats:
var event: Resource = ChartEventScript.new()
event.set("event_id", StringName("time_anchor_%d" % beat))
event.set("beat_index", beat)
event.set("event_type", &"time_anchor")
event.set("lead_beats", lead_beats)
events.append(event)
beat += safe_interval
return events
static func simulate_periodic_schedule(frequency_profile: Resource, streak: int, total_beats: int, explicit_beats: Array[int] = []) -> Array[int]:
var window_beats := 4
if frequency_profile != null and frequency_profile.get("window_beats") != null:
window_beats = maxi(1, int(frequency_profile.get("window_beats")))
var offsets: Array[int] = [window_beats]
if frequency_profile != null and frequency_profile.has_method("anchor_offsets_for_streak"):
var profile_offsets = frequency_profile.call("anchor_offsets_for_streak", streak)
if profile_offsets is Array and not profile_offsets.is_empty():
offsets = []
for offset: Variant in profile_offsets:
offsets.append(clampi(int(offset), 1, window_beats))
var scheduled: Array[int] = []
var origin := 0
while origin < total_beats:
var explicit_hit := false
for explicit_beat: int in explicit_beats:
if explicit_beat > origin and explicit_beat <= origin + window_beats:
origin = explicit_beat
explicit_hit = true
break
if explicit_hit:
continue
for offset: int in offsets:
var beat := origin + offset
if beat < total_beats:
scheduled.append(beat)
origin += window_beats
return scheduled
+1
View File
@@ -0,0 +1 @@
uid://dwl1tj4dusq35