feat: expand seasonal creature collection

This commit is contained in:
Alexander Sellite 2026-08-18 21:20:02 -04:00
parent 925bbb79e5
commit 95c6b8b053
415 changed files with 1688 additions and 389 deletions

View file

@ -49,7 +49,7 @@ func _run() -> void:
)
assert(player != null)
assert(catalog != null and catalog.candidates.size() == 316)
assert(LogbookCatalog.ordered_species(catalog.candidates).size() == 56)
assert(LogbookCatalog.ordered_species(catalog.candidates).size() == 63)
assert(sale_service != null)
assert(shop_service != null)
assert(session != null and session.is_host())

View file

@ -66,6 +66,9 @@ func _initialize() -> void:
assert(FishingShopStockType.get_price(&"standard_shovel") == 75)
assert(FishingShopStockType.get_stock_item_ids().has(&"standard_shovel"))
assert(FishingShopStockType.is_permanent_unlock(&"standard_shovel", shovel))
assert(FishingShopStockType.is_gathering_tool(&"crab_net"))
assert(FishingShopStockType.is_gathering_tool(&"standard_shovel"))
assert(not FishingShopStockType.is_gathering_tool(&"basic_fishing_rod"))
var wallet := PlayerWalletType.new()
wallet.current_balance = 250

View file

@ -15,6 +15,7 @@ const NetworkFishingServiceType = preload(
"res://network/network_fishing_service.gd"
)
const FishingSpotType = preload("res://fishing/fishing_spot.gd")
const CalendarSeasonType = preload("res://world/calendar_season.gd")
const Catalog: FishPoolType = preload("res://fish/pools/fish_catalog.tres")
const PondPool: FishPoolType = preload(
@ -69,6 +70,8 @@ const NEW_FRESH_WATER_IDS: Array[StringName] = [
&"chub_european", &"chub_flame", &"chub_lake", &"goldfish",
&"goldfish_bubbleeye", &"sauger", &"saugeye", &"trout_cutthroat",
&"trout_golden", &"trout_rainbow", &"trout_steelhead", &"walleye",
&"bowfin", &"sturgeon_lake", &"gar_longnose", &"paddlefish",
&"sturgeon_shovelnose", &"gar_spotted", &"lungfish_west_african",
]
@ -122,7 +125,7 @@ func _validate_weight_based_display_scale() -> void:
func _validate_catalog_and_pools() -> void:
assert(Catalog.candidates.size() == 316)
assert(PondPool.candidates.size() == 19)
assert(PondPool.candidates.size() == 26)
assert(OceanPool.candidates.size() == 34)
var active_count: int = 0
var inactive_count: int = 0
@ -134,6 +137,8 @@ func _validate_catalog_and_pools() -> void:
assert(not catalog_numbers.has(fish.catalog_number))
catalog_numbers[fish.catalog_number] = true
assert(not fish.logbook_fact.strip_edges().is_empty())
assert(CalendarSeasonType.is_valid_mask(fish.available_seasons))
assert(not fish.get_season_text().is_empty())
if fish.active:
active_count += 1
assert(fish.is_selectable())
@ -142,9 +147,46 @@ func _validate_catalog_and_pools() -> void:
inactive_count += 1
assert(not fish.is_selectable())
assert(fish.display_texture == null)
assert(active_count == 56)
assert(inactive_count == 260)
var inactive_fish: FishDataType = Catalog.get_fish_by_id(&"bowfin")
assert(active_count == 63)
assert(inactive_count == 253)
var chum: FishDataType = Catalog.get_fish_by_id(&"salmon_chum")
assert(chum != null)
assert(chum.get_season_text() == "fall")
assert(
chum.is_available_in_season(CalendarSeasonType.Season.FALL)
)
assert(
not chum.is_available_in_season(CalendarSeasonType.Season.SUMMER)
)
var chum_pool := FishPoolType.new()
chum_pool.candidates = [chum]
var seasonal_collection := CollectionLogType.new()
var seasonal_selector := FishSelectorType.new()
seasonal_selector.use_deterministic_test_seed = true
var summer_context := FishingContextType.new()
summer_context.water_type = WaterType.Type.SALT_WATER
summer_context.season = CalendarSeasonType.Season.SUMMER
seasonal_selector.begin_roll()
assert(
seasonal_selector.select_fish(
chum_pool,
summer_context,
seasonal_collection,
) == null
)
var fall_context := FishingContextType.new()
fall_context.water_type = WaterType.Type.SALT_WATER
fall_context.season = CalendarSeasonType.Season.FALL
seasonal_selector.begin_roll()
assert(
seasonal_selector.select_fish(
chum_pool,
fall_context,
seasonal_collection,
) == chum
)
seasonal_collection.free()
var inactive_fish: FishDataType = Catalog.get_fish_by_id(&"mudskipper_atlantic")
assert(inactive_fish != null and not inactive_fish.active)
var inactive_pool := FishPoolType.new()
inactive_pool.candidates = [inactive_fish]

View file

@ -181,7 +181,7 @@ func _run() -> void:
var invalid_state: Dictionary = valid_state.duplicate(true)
invalid_state["display_scale"] = 1000.0
assert(not NetworkFishShowcaseProtocol.validate_state(invalid_state))
assert(NetworkProtocol.PROTOCOL_VERSION == 7)
assert(NetworkProtocol.PROTOCOL_VERSION == 8)
assert(NetworkProtocol.ENET_CHANNEL_COUNT == 10)
assert(
NetworkProtocol.FISH_QUALITY_CAPABILITY

View file

@ -25,7 +25,7 @@ func _run() -> void:
_validate_mail_round_trip()
_validate_collection_mastery()
_validate_version_four_migration()
assert(NetworkProtocol.PROTOCOL_VERSION == 7)
assert(NetworkProtocol.PROTOCOL_VERSION == 8)
assert(NetworkProtocol.ENET_CHANNEL_COUNT == 10)
assert(NetworkProtocol.FISH_QUALITY_CAPABILITY == "fish_quality_v1")
print("Fish quality validation: PASS")

View file

@ -49,20 +49,20 @@ func _run() -> void:
assert(not hotbar.visible)
var entry_buttons: Dictionary = logbook.get("_entry_buttons")
assert(entry_buttons.size() == 19)
assert(entry_buttons.size() == 26)
await _capture_if_requested("-unknown")
logbook.call(
"_select_category", WaterType.Type.FRESH_WATER
)
await create_timer(0.25).timeout
assert((logbook.get("_entry_buttons") as Dictionary).size() == 19)
assert((logbook.get("_entry_buttons") as Dictionary).size() == 26)
await _capture_if_requested("-fresh")
logbook.call("_select_category", WaterType.Type.SALT_WATER)
await create_timer(0.25).timeout
assert((logbook.get("_entry_buttons") as Dictionary).size() == 34)
logbook.call("_select_category", WaterType.Type.FRESH_WATER)
await create_timer(0.25).timeout
assert((logbook.get("_entry_buttons") as Dictionary).size() == 19)
assert((logbook.get("_entry_buttons") as Dictionary).size() == 26)
player.collection_log.mark_discovered(&"bluegill")
await process_frame
logbook.call("_select_entry", &"bluegill", &"bluegill")
@ -110,7 +110,21 @@ func _validate_save_round_trip(
assert(catalog != null)
assert(catalog.candidates.size() == 316)
var active_species := LogbookCatalog.ordered_species(catalog.candidates)
assert(active_species.size() == 56)
assert(active_species.size() == 63)
# This test intentionally round-trips one catch for every active species.
# Give the fixture enough combined capacity, then bypass per-catch carried
# inventory admission while constructing it. The inventory layout still
# reconciles and persists the resulting placements.
player.inventory_layout.restore_backpack_level(
PlayerInventoryLayout.MAX_BACKPACK_LEVEL
)
player.cooler_capacity.restore_level(PlayerCoolerCapacity.MAX_LEVEL)
assert(
player.inventory_layout.get_backpack_level()
== PlayerInventoryLayout.MAX_BACKPACK_LEVEL
)
assert(player.cooler_capacity.get_level() == PlayerCoolerCapacity.MAX_LEVEL)
player.inventory.set_inventory_layout(null)
for index: int in 4:
_add_test_catch(player, active_species[index])
assert(save_manager.save_now())
@ -131,7 +145,7 @@ func _validate_save_round_trip(
assert(player.inventory.replace_all_catches(no_catches, 1))
assert(player.collection_log.replace_discovered_ids(no_discoveries))
assert(save_manager.load_player_data())
assert(player.inventory.get_all_catches().size() == 56)
assert(player.inventory.get_all_catches().size() == 63)
for fish: FishData in active_species:
assert(player.inventory.get_count(fish.id) == 1)
assert(player.collection_log.has_discovered(fish.id))
@ -151,6 +165,7 @@ func _validate_save_round_trip(
assert(catch_sprite != null)
assert(catch_sprite.texture == fish_catch.fish.display_texture)
player.end_catch_showcase(Callable(), true)
player.inventory.set_inventory_layout(player.inventory_layout)
func _add_test_catch(player: Player, fish: FishData) -> void:

View file

@ -42,7 +42,7 @@ func _run() -> void:
func _validate_catalog() -> void:
assert(CatalogResource.candidates.size() == 316)
var ordered := LogbookCatalog.ordered_species(CatalogResource.candidates)
assert(ordered.size() == 56)
assert(ordered.size() == 63)
var previous_number: int = 0
var catalog_numbers: Dictionary[int, bool] = {}
for fish: FishDataType in CatalogResource.candidates:
@ -152,7 +152,7 @@ func _validate_page() -> void:
var entries: Dictionary = page.get("_entry_buttons")
assert(
entries.size()
== (19 if category == LogbookCatalog.Category.FRESH_WATER else 34)
== (26 if category == LogbookCatalog.Category.FRESH_WATER else 34)
)
for fish: FishDataType in LogbookCatalog.ordered_species(
CatalogResource.candidates
@ -203,7 +203,7 @@ func _validate_page() -> void:
candidate.display_name
)
)
assert(silhouette_count == 53)
assert(silhouette_count == 60)
page.call("_select_category", LogbookCatalog.Category.SHELLFISH)
await create_timer(0.25).timeout

View file

@ -5,6 +5,12 @@ const PlayerScene: PackedScene = preload("res://player/player.tscn")
const CrabBrown: FishData = preload(
"res://fish/species/crab_brown/crab_brown.tres"
)
const ClamManila: FishData = preload(
"res://fish/species/clam_manila/clam_manila.tres"
)
const BeetleStagCommon: FishData = preload(
"res://fish/species/beetle_stag_common/beetle_stag_common.tres"
)
func _initialize() -> void:
@ -15,6 +21,18 @@ func _run() -> void:
var player := PlayerScene.instantiate() as Player
root.add_child(player)
await process_frame
assert(is_equal_approx(
Player.get_catch_texture_resolution_scale(CrabBrown.display_texture),
1.0,
))
assert(is_equal_approx(
Player.get_catch_texture_resolution_scale(ClamManila.display_texture),
20.0,
))
assert(is_equal_approx(
Player.get_catch_texture_resolution_scale(BeetleStagCommon.display_texture),
20.0,
))
player.set_process(false)
player.set_physics_process(false)
var animation_player := player.get_node(

View file

@ -157,12 +157,16 @@ func _run() -> void:
assert(presenter.size.is_equal_approx(Vector2(expected_viewport_size)))
var chat_panel := chat_ui.get_node("ChatPanel") as Control
var clock_panel := chat_ui.get_node("WorldClockPanel") as Control
var calendar_panel := chat_ui.get_node(
"WorldCalendarPanel"
) as Control
var status_effect_column := chat_ui.get_node(
"StatusEffectColumn"
) as Control
var weather_icon := chat_ui.get_node("WorldWeatherIcon") as Control
assert(chat_panel != null)
assert(clock_panel != null)
assert(calendar_panel != null)
assert(status_effect_column != null)
assert(weather_icon != null)
assert(chat_panel.visible)
@ -175,15 +179,25 @@ func _run() -> void:
ChatUI.CLOCK_EDGE_MARGIN,
ChatUI.CLOCK_EDGE_MARGIN,
)))
assert(calendar_panel.position.is_equal_approx(Vector2(
ChatUI.CLOCK_EDGE_MARGIN,
ChatUI.CLOCK_EDGE_MARGIN
+ ChatUI.CLOCK_SIZE.y
+ ChatUI.CALENDAR_TOP_GAP,
)))
assert(calendar_panel.size.is_equal_approx(ChatUI.CALENDAR_SIZE))
assert(is_equal_approx(
status_effect_column.position.x,
clock_panel.position.x
+ (ChatUI.CLOCK_SIZE.x - ChatUI.STATUS_EFFECT_ICON_SIZE.x) * 0.5,
calendar_panel.position.x
+ (
ChatUI.CALENDAR_SIZE.x
- ChatUI.STATUS_EFFECT_ICON_SIZE.x
) * 0.5,
))
assert(is_equal_approx(
status_effect_column.position.y,
clock_panel.position.y
+ ChatUI.CLOCK_SIZE.y
calendar_panel.position.y
+ ChatUI.CALENDAR_SIZE.y
+ ChatUI.STATUS_EFFECT_TOP_GAP,
))
assert(is_equal_approx(

View file

@ -8,6 +8,13 @@ const EXPECTED_BY_TYPE: Dictionary = {
&"clam_manila": 3,
&"beetle_stag_common": 3,
}
const WINTER_POPULATION: int = 5
const WINTER_BY_TYPE: Dictionary = {
&"crab_brown": 2,
&"clam_manila": 3,
}
const SUMMER_DATE_ID: String = "2026-08-18"
const WINTER_DATE_ID: String = "2026-12-18"
func _initialize() -> void:
@ -30,9 +37,12 @@ func _run_host() -> void:
var main: Node = await _create_initialized_main()
var session := main.get_node("%NetworkSession") as NetworkSession
var service: Node = main.get_node("%NetworkWorldSpawnService")
var world_time := main.get_node("%WorldTimeService") as WorldTimeService
assert(session.start_dedicated_host(TEST_PORT, 8, "127.0.0.1"))
assert(world_time.synchronize_calendar_time(12.0, SUMMER_DATE_ID))
assert(world_time.set_authoritative_time(12.0))
assert(session.set_host_open(true))
await _wait_for_population(service)
await _wait_for_population(service, EXPECTED_POPULATION)
_freeze_timed_entries(service)
_validate_population(service, true)
@ -49,6 +59,14 @@ func _run_host() -> void:
remote_peer_id,
NetworkProtocol.WORLD_SPAWN_CAPABILITY,
))
await create_timer(0.75).timeout
assert(world_time.synchronize_calendar_time(12.0, WINTER_DATE_ID))
await _wait_for_population(service, WINTER_POPULATION)
_validate_population(service, true, WINTER_BY_TYPE)
await create_timer(0.75).timeout
assert(world_time.synchronize_calendar_time(12.0, SUMMER_DATE_ID))
await _wait_for_population(service, EXPECTED_POPULATION)
_validate_population(service, true)
var disconnect_deadline: int = Time.get_ticks_msec() + 12000
while (
@ -76,6 +94,7 @@ func _run_client() -> void:
)
var session := main.get_node("%NetworkSession") as NetworkSession
var service: Node = main.get_node("%NetworkWorldSpawnService")
var world_time := main.get_node("%WorldTimeService") as WorldTimeService
var join_deadline: int = Time.get_ticks_msec() + 20000
while Time.get_ticks_msec() < join_deadline:
await process_frame
@ -87,11 +106,14 @@ func _run_client() -> void:
assert(session.supports_server_capability(
NetworkProtocol.WORLD_SPAWN_CAPABILITY
))
await _wait_for_population(service)
await _wait_for_population(service, EXPECTED_POPULATION)
_validate_population(service, false)
# Give the host process time to observe the authenticated peer before this
# deliberately short client validation disconnects.
await create_timer(0.75).timeout
await _wait_for_population(service, WINTER_POPULATION)
_validate_population(service, false, WINTER_BY_TYPE)
assert(world_time.get_calendar_date_id() == WINTER_DATE_ID)
await _wait_for_population(service, EXPECTED_POPULATION)
_validate_population(service, false)
assert(world_time.get_calendar_date_id() == SUMMER_DATE_ID)
print("World spawn multiplayer client validation: PASS")
session.disconnect_session("")
main.queue_free()
@ -101,20 +123,27 @@ func _run_client() -> void:
quit()
func _wait_for_population(service: Node) -> void:
func _wait_for_population(service: Node, expected_population: int) -> void:
var deadline: int = Time.get_ticks_msec() + 12000
while Time.get_ticks_msec() < deadline:
await process_frame
if (service.get("_entities") as Dictionary).size() == EXPECTED_POPULATION:
if (service.get("_entities") as Dictionary).size() == expected_population:
return
assert(false, "Timed out waiting for the world spawn population.")
func _validate_population(service: Node, expect_authoritative_state: bool) -> void:
func _validate_population(
service: Node,
expect_authoritative_state: bool,
expected_by_type: Dictionary = EXPECTED_BY_TYPE,
) -> void:
var entities: Dictionary = service.get("_entities")
var presentations: Dictionary = service.get("_presentations")
assert(entities.size() == EXPECTED_POPULATION)
assert(presentations.size() == EXPECTED_POPULATION)
var expected_population: int = 0
for amount: int in expected_by_type.values():
expected_population += amount
assert(entities.size() == expected_population)
assert(presentations.size() == expected_population)
var counts: Dictionary = {}
for state: Dictionary in entities.values():
var type_id := state.get("type_id") as StringName
@ -137,7 +166,7 @@ func _validate_population(service: Node, expect_authoritative_state: bool) -> vo
else:
assert(entry.get_movement_speed_for_quality(quality) > 0.0)
assert(entry.get_scare_radius_for_quality(quality) > 0.0)
assert(counts == EXPECTED_BY_TYPE)
assert(counts == expected_by_type)
func _freeze_timed_entries(service: Node) -> void:

View file

@ -12,10 +12,11 @@ const WorldGatherableType = preload("res://gathering/world_gatherable.gd")
const CRAB_PIXEL_SIZE: float = 0.0005
const REDUCED_CATCH_RING_RADIUS: float = 0.175
const NET_STRIKE_MARKER_DISTANCE: float = 0.85
const CalendarSeasonType = preload("res://world/calendar_season.gd")
func _initialize() -> void:
assert(NetworkProtocol.PROTOCOL_VERSION == 7)
assert(NetworkProtocol.PROTOCOL_VERSION == 8)
assert(
NetworkWorldSpawnProtocol.CAPABILITY
== NetworkProtocol.WORLD_SPAWN_CAPABILITY
@ -67,6 +68,8 @@ func _validate_catalog_statuses() -> void:
assert(not clam.catch_data.is_fishable())
var beetle: GatherableData = Gatherables.get_entry(&"beetle_stag_common")
assert(beetle != null and beetle.is_available())
assert(beetle.is_available(CalendarSeasonType.Season.SUMMER))
assert(not beetle.is_available(CalendarSeasonType.Season.WINTER))
assert(beetle.catch_data.collection_method == FishData.CollectionMethod.NET)
assert(beetle.required_tool_id == &"crab_net")
assert(beetle.spawn_anchor_set_id == &"starter_reachable_tree_trunks")
@ -104,6 +107,12 @@ func _validate_catalog_statuses() -> void:
assert(available.has(brown))
assert(available.has(clam))
assert(available.has(beetle))
var winter_available: Array[GatherableData] = (
Gatherables.get_available_entries(CalendarSeasonType.Season.WINTER)
)
assert(winter_available.has(brown))
assert(winter_available.has(clam))
assert(not winter_available.has(beetle))
var rng := RandomNumberGenerator.new()
rng.seed = 24680
var captured_delay: float = brown.get_respawn_delay(&"captured", rng)

View file

@ -53,6 +53,9 @@ func _run_host() -> void:
remote_peer_id, NetworkProtocol.WORLD_WEATHER_CAPABILITY
))
assert(world_time.get_phase() == WorldTimeService.Phase.DUSK)
assert(WorldTimeService.is_valid_calendar_date_id(
world_time.get_calendar_date_id()
))
await create_timer(1.0).timeout
assert(world_time.set_authoritative_time(UPDATED_HOST_TIME))
assert(world_weather.set_authoritative_weather(
@ -111,6 +114,10 @@ func _run_client() -> void:
world_time.get_time_hours(), INITIAL_HOST_TIME
) <= TIME_TOLERANCE_HOURS)
assert(world_time.get_phase() == WorldTimeService.Phase.DUSK)
var expected_date_id: String = WorldTimeService.date_id_from_datetime(
Time.get_datetime_dict_from_system(false)
)
assert(world_time.get_calendar_date_id() == expected_date_id)
var weather_deadline: int = Time.get_ticks_msec() + 8000
while (
Time.get_ticks_msec() < weather_deadline
@ -122,15 +129,31 @@ func _run_client() -> void:
var chat_ui := game_ui.get_node("%ChatUI") as Control
var clock_panel := chat_ui.get_node("WorldClockPanel") as PanelContainer
var clock_label := clock_panel.get_node("WorldClockLabel") as Label
var calendar_panel := (
chat_ui.get_node("WorldCalendarPanel") as PanelContainer
)
var calendar_label := (
calendar_panel.get_node("WorldCalendarLabel") as Label
)
var weather_icon := chat_ui.get_node("WorldWeatherIcon") as WeatherIcon
var chat_panel := chat_ui.get_node("ChatPanel") as PanelContainer
assert(clock_panel.visible)
assert(clock_label.text == world_time.get_clock_text())
assert(clock_label.text.ends_with(" pm"))
assert(calendar_panel.visible)
assert(calendar_label.text == world_time.get_calendar_text())
assert(not calendar_label.text.is_empty())
assert(weather_icon.visible)
assert(weather_icon.get_weather() == WorldWeatherService.Weather.RAINY)
assert(is_equal_approx(clock_panel.position.y, 10.0))
assert(is_equal_approx(weather_icon.position.y, 10.0))
assert(is_equal_approx(
calendar_panel.position.y,
clock_panel.position.y
+ clock_panel.size.y
+ ChatUI.CALENDAR_TOP_GAP,
))
assert(calendar_panel.size.is_equal_approx(ChatUI.CALENDAR_SIZE))
assert(clock_panel.position.y + clock_panel.size.y < chat_panel.position.y)
assert(not chat_ui.has_method("_handle_chat_command"))
@ -159,11 +182,17 @@ func _run_client() -> void:
await process_frame
assert(clock_panel.position.x > 1000.0)
assert(weather_icon.position.x < clock_panel.position.x)
assert(is_equal_approx(
calendar_panel.position.x, weather_icon.position.x
))
assert(is_equal_approx(clock_panel.position.y, 10.0))
chat_ui.call("set_dock_right", false)
await process_frame
assert(clock_panel.position.x < 20.0)
assert(weather_icon.position.x > clock_panel.position.x)
assert(is_equal_approx(
calendar_panel.position.x, clock_panel.position.x
))
assert(is_equal_approx(clock_panel.position.y, 10.0))
print("World time multiplayer client validation: PASS")
session.disconnect_session("")

View file

@ -15,6 +15,7 @@ const FishableWaterRegionType = preload(
"res://world/fishable_water_region.gd"
)
const WeatherIconType = preload("res://ui/weather_icon.gd")
const CalendarSeasonType = preload("res://world/calendar_season.gd")
const Catalog: FishPoolType = preload("res://fish/pools/fish_catalog.tres")
const SaltWaterMaterial: ShaderMaterial = preload(
@ -57,6 +58,35 @@ func _validate_clock_boundaries_and_duration() -> void:
"year": 2026, "month": 8, "day": 18,
}) == "2026-08-18"
)
assert(WorldTimeServiceType.is_valid_calendar_date_id("2026-08-18"))
assert(WorldTimeServiceType.is_valid_calendar_date_id("2028-02-29"))
assert(not WorldTimeServiceType.is_valid_calendar_date_id("2026-02-29"))
assert(not WorldTimeServiceType.is_valid_calendar_date_id("2026-13-01"))
assert(not WorldTimeServiceType.is_valid_calendar_date_id("2026-8-18"))
assert(
WorldTimeServiceType.format_calendar_date("2026-08-18")
== "tuesday, august 18"
)
assert(
CalendarSeasonType.from_date_id("2026-03-01")
== CalendarSeasonType.Season.SPRING
)
assert(
CalendarSeasonType.from_date_id("2026-06-01")
== CalendarSeasonType.Season.SUMMER
)
assert(
CalendarSeasonType.from_date_id("2026-09-01")
== CalendarSeasonType.Season.FALL
)
assert(
CalendarSeasonType.from_date_id("2026-12-01")
== CalendarSeasonType.Season.WINTER
)
assert(
CalendarSeasonType.from_date_id("invalid")
== CalendarSeasonType.UNKNOWN
)
assert(
WorldTimeServiceType.calendar_cycle_id_from_datetime({
"year": 2026, "month": 8, "day": 18,
@ -99,7 +129,11 @@ func _validate_clock_boundaries_and_duration() -> void:
var clock := WorldTimeServiceType.new()
root.add_child(clock)
clock.begin_test_session(8.0)
clock.begin_test_session(8.0, "2026-08-18")
assert(clock.get_calendar_date_id() == "2026-08-18")
assert(clock.get_calendar_text() == "tuesday, august 18")
assert(clock.get_season() == CalendarSeasonType.Season.SUMMER)
assert(clock.get_season_text() == "summer")
clock.advance_time(12.0 * 60.0 * 60.0)
assert(is_equal_approx(clock.get_time_hours(), 20.0))
assert(clock.is_night_period())
@ -214,7 +248,7 @@ func _validate_fishing_availability() -> void:
func _validate_fishing_spot_context() -> void:
var clock := WorldTimeServiceType.new()
clock.begin_test_session(20.25)
clock.begin_test_session(20.25, "2026-12-18")
var fishing_spot := FishingSpotType.new()
fishing_spot.set("_world_time", clock)
var region := FishableWaterRegionType.new()
@ -223,6 +257,7 @@ func _validate_fishing_spot_context() -> void:
var dusk_context: FishingContext = fishing_spot.build_network_context(region)
assert(dusk_context.is_night)
assert(dusk_context.is_day_night_transition)
assert(dusk_context.season == CalendarSeasonType.Season.WINTER)
clock.synchronize_time(14.0)
var day_context: FishingContext = fishing_spot.build_network_context(region)
assert(not day_context.is_night)
@ -236,16 +271,25 @@ func _validate_network_snapshot_bounds() -> void:
assert(NetworkWorldTimeServiceType.validate_snapshot({
"session_id": "session",
"time_hours": 8.25,
"calendar_date_id": "2026-08-18",
"sequence": 1,
}))
assert(not NetworkWorldTimeServiceType.validate_snapshot({
"session_id": "session",
"time_hours": 24.0,
"calendar_date_id": "2026-08-18",
"sequence": 1,
}))
assert(not NetworkWorldTimeServiceType.validate_snapshot({
"session_id": "",
"time_hours": 8.0,
"calendar_date_id": "2026-08-18",
"sequence": 1,
}))
assert(not NetworkWorldTimeServiceType.validate_snapshot({
"session_id": "session",
"time_hours": 8.0,
"calendar_date_id": "2026-02-29",
"sequence": 1,
}))