Commit Graph

686 Commits

Author SHA1 Message Date
gamer147
998402ebc3 refactor(tutorial-presents): promote static catalogue to seed-driven TutorialPresentEntries table
The five tutorial gifts every fresh viewer is given at signup used to live as a
static C# array in SVSim.Database/SeedData/TutorialPresents.cs — outside the
seed-JSON pipeline used by all 40+ other globals tables. Editing a gift required
a code change + rebuild instead of a JSON edit + bootstrap re-run.

Now authored in SVSim.Bootstrap/Data/seeds/tutorial-presents.json and loaded into
a new TutorialPresentEntries table via TutorialPresentsImporter (clear-and-rewrite,
mirroring HomeDialogs). ViewerRepository.RegisterAnonymousViewer reads the table
at signup and projects each row into a ViewerPresent with Source="tutorial".

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-09 09:53:10 -04:00
gamer147
7118b92522 refactor(pack): type PackChildGachaEntry.TypeDetail as CardPackType enum 2026-06-09 08:48:16 -04:00
gamer147
833bd85d36 data(seed): regenerate packs.json including free-pack children from event-crate capture 2026-06-08 21:51:05 -04:00
gamer147
57d231cd56 feat(pack): /pack/open supports type_detail=10 FREE_PACKS with per-campaign daily quota 2026-06-08 21:43:04 -04:00
gamer147
6c7e8ae8ad feat(pack): /pack/info filters spent free-pack children and emits campaign metadata 2026-06-08 21:38:26 -04:00
gamer147
b9c29b53d9 feat(pack): add free-pack metadata fields to PackChildGachaDto 2026-06-08 21:34:49 -04:00
gamer147
7d7cf699f8 feat(viewer): add FreePackClaims owned collection for daily-free quota tracking 2026-06-08 21:34:20 -04:00
gamer147
d762c5766f feat(pack): persist daily_free_gacha_count on PackChildGachaEntry 2026-06-08 21:31:02 -04:00
gamer147
7e4a9654b2 feat(seed): add DailyFreeGachaCount to PackChildGachaSeed 2026-06-08 21:23:14 -04:00
gamer147
feee6e7c09 test(tutorial-e2e): seed tutorial presents — RegisterViewer no longer auto-seeds 2026-06-08 20:51:42 -04:00
gamer147
83e89455e2 test(signup): assert tutorial presents seeded by RegisterAnonymousViewer 2026-06-08 20:46:27 -04:00
gamer147
7a582f310e test(gift): prod-URL coverage and state=3 delete behavior 2026-06-08 20:45:49 -04:00
gamer147
f1d881b26a fix(gift): drop RowVersion (SQLite incompatible) + restore wire reward_type map
[Timestamp] byte[] doesn't work under SQLite (the test backend) — EF
expects the DB to populate it on insert, but SQLite has no equivalent
of Postgres's xmin. The WHERE Status = Unclaimed filter plus
IInventoryService's viewer-level concurrency is the practical defense;
RowVersion was only a backstop. Regenerated the migration without the
RowVersion column.

Wire reward_type on the gift endpoint uses a gift-specific scheme that
diverges from UserGoodsType for currencies: wire 1 = Crystal (enum=2),
wire 9 = Rupy (enum=9), wire 4 = Item (enum=4). A naked cast resolves
wire 1 to UserGoodsType.RedEther and silently grants the wrong wallet
— restored the explicit WireRewardTypeToUserGoodsType map from the old
tutorial controller.

Retrofits existing GiftControllerTests to call SeedTutorialPresentsAsync
on the new helper (RegisterViewer doesn't auto-seed; only the prod
signup path does). All 7 existing tests pass.
2026-06-08 20:44:52 -04:00
gamer147
ca36792be3 feat(db): migration — add viewer_presents, drop viewer_claimed_tutorial_gifts 2026-06-08 20:38:44 -04:00
gamer147
6098682162 refactor(db): remove ViewerClaimedTutorialGift — replaced by ViewerPresent.Status 2026-06-08 20:38:09 -04:00
gamer147
fafd7ea183 test(gift): add SeedTutorialPresentsAsync helper 2026-06-08 20:37:26 -04:00
gamer147
2b35ae0890 feat(gift): unified GiftController over ViewerPresent + route aliases 2026-06-08 20:36:44 -04:00
gamer147
c1d7cd2441 feat(dtos): add PresentMapper.ToWire(ViewerPresent) 2026-06-08 20:35:59 -04:00
gamer147
bf51dabcff refactor(dtos): promote PresentDto to Common/ 2026-06-08 20:35:42 -04:00
gamer147
2ce399ff87 feat(signup): seed tutorial gifts as ViewerPresent rows on /tool/signup 2026-06-08 20:34:48 -04:00
gamer147
f991ef762f feat(db): add TutorialPresents seed list 2026-06-08 20:34:11 -04:00
gamer147
eea596c6ec feat(db): wire ViewerPresent into SVSimDbContext with indexes 2026-06-08 20:33:50 -04:00
gamer147
a6a8c6b1a4 feat(db): add ViewerPresent entity for unified gift inbox 2026-06-08 20:33:10 -04:00
gamer147
ce32a9c6b7 feat(home-dialog): seed file + importer + bootstrap hookup
Mirrors banners pattern: clear-and-rewrite from per-table JSON seed.
Ships one entry pointing at parent_gacha_id 80032 to match the
2026-06-03 prod capture.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 18:57:38 -04:00
gamer147
9d6a5cc3b9 feat(home-dialog): populate home_dialog_list on /mypage/index
Walk-down behavior: each call emits the highest-priority unfired
active dialog; subsequent calls walk to the next-priority entry.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 18:55:48 -04:00
gamer147
7e757ebcd2 feat(home-dialog): per-session suppression tracker
Singleton keyed by ShortUdid; lock on per-viewer set to avoid
cross-viewer contention. Process lifetime — restart re-fires.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 18:53:33 -04:00
gamer147
6d60edaa2a feat(home-dialog): IGlobalsRepository.GetActiveHomeDialogsAsync
Window is [begin, end) — exclusive upper bound. Ordered priority-DESC
then Id-ASC so the controller can break on the first match.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 18:52:32 -04:00
gamer147
7a82f4e189 feat(home-dialog): add HomeDialogEntry entity + migration
DDL-only per migrations-are-ddl-only convention. Seeded by
SVSim.Bootstrap MyPageGlobalsImporter (T5) — no HasData.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 18:51:12 -04:00
gamer147
d3488c3bc6 fix(viewer): default ClassExp.Level to 1 for new viewers
Client RankMatchUI.onOpen indexes _classCharaExpList[level - 1]
unconditionally; level 0 (the prior default) throws IOOR.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 18:49:45 -04:00
gamer147
20ddba4c5f Merge battle-engine-extraction: engine port + multi-instancing
Lands the Phase 1 + Phase 2 + multi-instancing migration as one mergepoint.

Phase 1 — engine extraction (M1-M14): client's BattleManagerBase ported
headless under SVSim.BattleEngine; deterministic emit; authoritative RNG
seam (Q-RNG/F2); single-card resolution set proven.

Phase 2 — headless conductor (M-HC-0..4 + M-HC-exit): receive conductor
runs headless inside the battle node via no-op view shims + InstantVfx
rule (zero Engine/*.cs edits, check_drift.py clean). Six structural
shadow-vs-wire divergences resolved against live PvP traffic.

Multi-instancing: per-battle engine statics now AsyncLocal-scoped via
BattleAmbientContext (Mgr, GameMgr, ViewerId, IsForecast, IsRandomDraw,
RealTimeNetworkAgent, BattleRecoveryInfo). EngineSessionGate deleted;
SessionBattleEngine wraps all entry points; MultiInstanceEngineTests is
the regression oracle.

Parallelism hardening: Resources._loaded + GameObject._components shim
Dictionaries -> ConcurrentDictionary; Wizard.LocalLog mutations gated by
single static lock. SVSim.BattleEngine.Tests now runs under
ParallelScope.Fixtures; StressN parallelizes Setup AND Drive.

Pending: live two-pair PvP smoke (Task 10 of the multi-instancing plan).

Suite: SVSim.UnitTests 1054/1054; SVSim.BattleEngine.Tests 59/2 (skips
pre-existing). Audit script tools/engine-port/audit-static-writes.ps1
green.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-08 10:21:09 -04:00
gamer147
5a23f93152 docs(engine-ambient): explain why _components GetOrAdd factory is contention-safe
Reviewer noted the factory may be invoked more than once under contention.
Document the analysis inline so a future reader doesn't have to redo it:
the discarded instance's mutations land on private fields of a soon-unreachable
object, and the only shared sentinel (_noopViewMaterial) is read-only.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-08 08:25:34 -04:00
gamer147
fbac66fd0b chore(engine-ambient): harden shim + LocalLog statics for fixture parallelism
Follow-up to the multi-instancing migration. Wraps the process-shared engine
statics that aren't ambient-fronted but race between concurrent battles:

- UnityEngine.Resources._loaded: Dictionary -> ConcurrentDictionary.GetOrAdd
  (the shared prefab cache keyed by path; concurrent first-misses produced
  duplicate GameObjects + Dictionary corruption)
- UnityEngine.GameObject._components: Dictionary -> ConcurrentDictionary with
  Interlocked.CompareExchange init (Resources.Load returns SHARED prefab
  GameObjects, so two engines' Setup() can race on the same _components map
  — surfaced as "Operations that change non-concurrent collections" crashes
  during BattleManagerBase ctor's GetComponent<T>() chain)
- Wizard.LocalLog: single static lock around all mutating entry points
  (StringBuilder _lastTraceLogStringBuilder + ~12 mutable string/bool/int
  scratch fields; serializing the trace-log surface is cheap since logging
  is not the hot path)

Flips SVSim.BattleEngine.Tests assembly Parallelizable scope from Self to
Fixtures and restructures MultiInstanceEngineTests.StressN_BaselineMatches so
Setup runs INSIDE Task.Run (was previously serialized as a workaround for the
LocalLog races). The fixture is also lifted to ParallelScope.All so the
two-engines and stress tests can run alongside each other.

Suite fully green under fixture parallelism (59/0/2 across 3 consecutive runs);
SVSim.UnitTests still 1054/0/0 — true multi-instance correctness is now proved
end-to-end in tests rather than gated behind a serial workaround.

Manifest sha refresh + new patch artifact for the LocalLog edit (decomp-origin);
the two shim files are authored, so no metadata update is needed for them.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-08 08:02:49 -04:00
gamer147
45344e6d83 chore(engine-ambient): audit script for static-write regressions
Step 9 of multi-instancing migration. PowerShell audit fails CI if anything
references the deleted BattleManagerBase.main field or introduces a new
Thread() outside the LeanThreadPool allowlist.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-07 23:31:09 -04:00
gamer147
ab4545b274 test(engine-ambient): tighten MultiInstanceEngineTests post-setup assertions
Replace trivially-true Pp>=0 with concrete post-Setup pins (LeaderLife=20,
Pp=0, HandCount=3). Drop the unused seed parameter from SampleDeck - every
call already returned the same vanilla deck, and the StressN test name 'Random
Decks' overpromised. The cross-contamination property the test pins (parallel
LeaderLife[] equals sequential LeaderLife[]) holds with identical decks +
distinct masterSeeds, which is what's actually being verified.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-07 23:28:21 -04:00
gamer147
c789d836f1 feat(engine-ambient): delete static fallbacks; add MultiInstanceEngineTests
Step 8 (final) of multi-instancing migration. All per-battle statics now
require a BattleAmbient scope — unwrapped writes throw InvalidOperationException
(fail-fast forcing function). MultiInstanceEngineTests proves correctness:
two parallel battles resolve independently, N=4/8/16 stress matches sequential
baseline, GameMgr.GetIns throws without scope.

Migration complete. EngineSessionGate gone. Suite fully green.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-07 23:19:37 -04:00
gamer147
9e93a7b198 refactor(engine-ambient): wrap residual UnitTests + delete EngineSessionGate
Step 7 of multi-instancing migration. Residual SVSim.UnitTests that touch
engine code directly are wrapped in TestBattleScope. EngineSessionGate is
deleted along with the _engineOwned bookkeeping in BattleSession; engine
setup is unconditional now that per-battle state is isolated on the ambient.
Gate-specific fallback branches in BattleSession.ShadowIngest are simplified.

Suite fully green (SVSim.UnitTests, SVSim.BattleEngine.Tests).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-07 22:43:18 -04:00
gamer147
8af1be6555 test(engine-ambient): TestBattleScope + HeadlessFixture split for multi-instance
Step 6 of multi-instancing migration. HeadlessEngineEnv.EnsureInitialized
is split into EnsureProcessGlobals (idempotent, process-once) +
SeedCharaIdsOnCurrentAmbient (per-test). New TestBattleScope IDisposable
sets up a fresh BattleAmbientContext per test. NonParallelizable removed
from converted classes; assembly-level Parallelizable(Fixtures) enabled.

SVSim.BattleEngine.Tests fully green under parallel test execution.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-07 22:24:21 -04:00
gamer147
1ba75c565a refactor(engine-ambient): GameMgr.GetIns throws Require; wrap SessionBattleEngine entry points
Step 5 of multi-instancing migration. GameMgr.GetIns() now resolves through
BattleAmbient.Require() (throws when no scope active — fail-fast since engine
callers unconditionally dereference). SessionBattleEngine now owns a single
BattleAmbientContext, pushed via BattleAmbient.Enter at Setup/Receive/all
~30 read accessors and Debug* seams.

EngineGlobalInit.WirePerSessionGameMgr extracted out of the _done-gated block:
GameMgr is now per-session (ctx.GameMgr is a fresh `new()` per SessionBattleEngine),
so the DataMgr chara ids + NetworkUserInfoData seeding must run every Setup, not
process-once. The wiring itself is already idempotent. Without this, second-or-
later sessions in a process NRE in NetworkBattleManagerBase.CreateBackgroundId.

Expected state: SVSim.BattleEngine.Tests have known-failing tests that don't
go through SessionBattleEngine (Task 6 wraps HeadlessFixture). SVSim.UnitTests
mostly recover; residual failures (deal-frame Accepted:false in conductor
integration tests) are captured in
data_dumps/task5-test-output/failing-tests-after-task5-node-postwrap.txt for
Task 7.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-07 21:56:34 -04:00
gamer147
18da7fd19e test(engine-ambient): cover BattleRecoveryInfo setter ambient write-through
Setter is the asymmetric one (write-through inside scope, unlike ViewerId's
no-op-in-scope) — adding parity with the SetRealTimeNetworkBattle ambient
setter test to catch future regressions if the routing branch is touched.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-07 21:42:55 -04:00
gamer147
fe146fde50 refactor(engine-ambient): ViewerId/RealTimeNetworkAgent/BattleRecoveryInfo read ambient first
Step 4 of multi-instancing migration. Three additional per-battle statics
front-fronted by BattleAmbient.Current, each with a static fallback for
unwrapped callers. ViewerId's SavedataManager-persisting setter is preserved
on the fallback path; inside a scope, the setter is a no-op (the per-battle
perspective is fixed at scope entry).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-07 21:37:58 -04:00
gamer147
4e756a6c46 refactor(engine-ambient): BattleManagerBase.GetIns reads ambient first, static fallback
Step 3 of multi-instancing migration. The dominant per-battle singleton now
resolves through BattleAmbient.Current.Mgr when a scope is active. The legacy
'main' field is renamed _mainFallback and retained for unwrapped callers
(tests, anything not yet scope-wrapped). GetIns() still returns null when
neither is set, preserving the '?.Foo ?? default' patterns in engine code.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-07 21:23:27 -04:00
gamer147
92da7819f4 chore(engine-ambient): refresh BattleManagerBase manifest sha + add patch artifact
Hygiene fixup for the IsForecast/IsRandomDraw ambient conversion in 3b5f2e1.
The manifest sha was stale (pointed at the pre-ambient RNG-virtual-patched
contents) and the change had no companion .patch artifact alongside
BattleManagerBase.rng-virtual.patch. Follow established convention.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-07 21:16:57 -04:00
gamer147
3b5f2e18b3 refactor(engine-ambient): IsForecast/IsRandomDraw read ambient first, static fallback
Step 2 of multi-instancing migration. Both flags now resolve through
BattleAmbient.Current when a scope is active, otherwise hit a static fallback
that preserves today's behavior unchanged for unwrapped callers.

Suite green: SVSim.BattleEngine.Tests pass; SVSim.UnitTests baseline holds.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-07 21:11:49 -04:00
gamer147
4829e8c263 feat(engine-ambient): add BattleAmbientContext + AsyncLocal scope
Step 1 of the engine multi-instancing migration. Standalone infrastructure;
no engine static reads/writes through it yet. Scope is reentrant (restores
prior on dispose), AsyncLocal flows across awaits, and isolated between
concurrent Task.Run flows.

See docs/superpowers/specs/2026-06-07-engine-multi-instancing-design.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-07 21:04:21 -04:00
gamer147
addeb021d2 fix(battlenode): shadow engine tracks live PvP wire-truth (full battle, multiple bid regressions)
Six distinct fixes accumulated over live-test iterations against four bids
(654473755566, 806245601092, 283192092460, 131549100204, 799755786270) — together
they take the shadow engine from "throws on the first non-mulligan play" to
"survives a full PvP battle, only weird-edge-case Unity touches still left to whack".

1. Engine StableRandom seed aligned with clients' Matched.seed
   (BattleSession.EnsureEngineSetup, NodeNativeBattleHarness.Create). Clients seed
   _stableRandom with BattleSeeds.Stable(masterSeed) (the value the node ships in
   Matched.seed); we were passing the RAW masterSeed to engine.Setup, so every
   StableRandom call diverged from call #1 onward — every turn-1+ draw picked a
   different deck position than the clients. Verified Stable(1184631275)=1543475792
   matches the wire on bid 654473755566.

2. SeedDeck advances cardTotalNum to deck.Count+1 + pins BattleStartDeckCardList.
   Mirrors SBattleLoad.InitPlayer's tail (SBattleLoad.cs:1292). Without it,
   skill-generated tokens auto-assigned Index 0,1,... and COLLIDED with deck-loaded
   indices 1..40 — silent until something addressed the deck card with the
   colliding Index (Hoverboarder at deck idx 1 + a token at engine Index 1 made
   GetBattleCardIdx's SingleOrDefault throw on bid 806245601092).

3. BattleCardView.GameObject lazily non-null in the shim (ViewUiTouchStubs.cs).
   The IsRecovery card-create delegate (NetworkBattleManagerBase.cs:379) passes
   null cardGameObject; Skill_metamorphose.cs:147 in the in-play branch then NRE'd
   on `metamorphosedCard.BattleCardView.GameObject.transform.rotation = identity`,
   a purely cosmetic touch with no game-state implication. Bid 283192092460:
   Petrification on a board follower.

4. TranslateChoiceKeyAction unwraps wrapped selectCard on shadow ingest
   (SessionBattleEngine.cs, sibling to TranslateTargetOwners). Live sender-send
   wires Choice plays as selectCard:{cardId:[...], open:0}; engine's
   ConvertToListInt does `value as List<object>` — a Dict casts to null and
   foreach NREs. The receiver's swallow-all catch (NetworkBattleReceiver.cs:1255)
   logs to Debug.LogError + LocalLog — both shimmed/no-op'd headlessly — and
   returns false, but Receive calls ReceivedMessage with checkBreakData:false so
   the false isn't propagated. The play continues with choiceIdList=[], the chosen
   branch never resolves, the played card stays in hand; a later targeted play
   (A's bounce on B's "board" idx 20) then can't find the target → NRE on null in
   ActionProcessor.PlayCard:407. Bid 131549100204: B's Resonance + A's bounce.
   Opponent-relay path is unaffected — node strips selectCard from broadcasts.

5. HeadlessHandViewStub overrides HandUnfocus/HandFocus/FocusRearrangeHandHand
   to return NullVfx. CreateHandControl returns null in headless; the base
   methods unconditionally deref `_handControl.SetHandState(...)`. A follower
   with a when_spell_play Heal trigger fired on its leader for amount 0 — even
   a 0-heal drives ApplyHealing → CreatePullHandInVfx → HandUnfocus → NRE.
   Bid 799755786270: two consecutive spell plays both crashed this stack.
   Added InternalsVisibleTo("SVSim.BattleEngine.Tests") so the shim-level
   regression tests can pin the no-op contracts directly.

Plus the previous-session fixes carried in this same uncommitted state
(see docs/superpowers/plans/2026-06-07-shadow-engine-desync-handoff.md):
  - doesPlayerGoFirst:true + mgr.IsFirst:true (turn-1 draw count correct
    per seat)
  - RecoveryOperationCollection.PlayHandCardOperation routes all type:30
    through PlaySkillSelectHandCardOperation (skips the two-phase user-select
    guard that aborts targeted spells in recovery)
  - ShadowFeed + ToRawBody: server-generated typed bodies (DealBody, etc.)
    converted to RawBody before engine.Receive (`env.Body as RawBody`
    returned null for typed bodies)
  - Ready idxChangeSeed seeds A's XorShift via the receiver; B's seed is
    injected via SeedOppoIdxChange (BattleSeeds.IdxChange + viewerId)
  - ReadySpin defaulted to 0 (was 243) — non-zero double-cranks the shadow
    which ingests BOTH sides' Ready frames on one stream

Test counts: SVSim.UnitTests 1054/1054, SVSim.BattleEngine.Tests 34/34.

Open: known-residual Unity touches are individual whack-a-mole now (per-card
skill edge cases), not the structural divergences fixed here.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-07 19:05:07 -04:00
gamer147
2a8c44a6d7 build(battleengine): pin LangVersion 12.0 (was 'latest') so C# 14's 'field' keyword doesn't break the decompiled engine under newer SDKs 2026-06-07 08:03:01 -04:00
gamer147
25751462f4 fix(battlenode): translate live isSelf target frames to engine vid shape on ingest (live PvP fidelity)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 07:44:53 -04:00
gamer147
97e4664cc4 docs(battlenode): regen-guard banners on hand-edited .g.cs + accessor-band null-policy invariant (M-HC-4 final review) 2026-06-07 01:02:00 -04:00
gamer147
8bd8d1db2f docs(battlenode): correct EVOLUTION_SELECT deferral rationale — skill data is present (M-HC-4) 2026-06-07 00:52:22 -04:00
gamer147
f1c96ed37d refactor(battlenode): M-HC-4 cleanup — EpCount rename, dedupe evolve-ramp, drop tautological guard
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 00:47:33 -04:00