Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 39 additions & 3 deletions src/entities/spot/sim-stream-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,25 @@ import type { PersonaArchetype, PersonaIntent } from '@/entities/persona/types';

export type PlaceType = 'region' | 'spot';

export type SimMapAnchor = {
type?: string;
lat: number;
lng: number;
region_id?: string;
category?: string;
confidence?: number;
match_reason?: string;
};

export type SimHotspotSignal = {
signal_type?: 'teach_spot' | string;
state?: 'forming' | 'recruiting' | 'active' | string;
host_persona_type?: string;
participant_target_count?: number;
reason_tags?: string[];
region_character?: Record<string, number>;
};

export type PlaceGeometry = {
place_id: string;
place_type: PlaceType;
Expand All @@ -30,6 +49,8 @@ export type PlaceGeometry = {
intent?: PersonaIntent;
/** spot 일 때 UI 카드 제목(template 기반 합성). */
title?: string;
/** simulator 가 제공한 public/proxy map anchor. spot 좌표보다 우선 사용한다. */
map_anchor?: SimMapAnchor;
};

// ─── Agent ───────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -69,8 +90,14 @@ export type SimManifest = {
approved_spot_count: number;
/** 향후 'all' 옵션 대비. 현재는 항상 'published_only'. */
filter_kind: SimRunFilterKind;
/** 전체 tick 수(예: 48). 재생 길이 표시용. */
/** 한 재생 사이클의 tick 수. loop_period_ticks 가 없으면 이 값을 loop period 로 사용한다. */
total_ticks: number;
/** total_ticks 이후에도 이전 사이클 잔상/종료 상태를 유지할 때의 loop period. */
loop_period_ticks?: number;
/** loop 이후 이전 사이클을 겹쳐 보여줄 tail 길이. */
projection_tail_ticks?: number;
/** tail 데이터 로드를 허용하는 최대 raw tick. inclusive. */
max_projected_tick?: number;
/** 클라이언트 디폴트 1 tick 길이. 사용자가 prop 으로 오버라이드 가능. */
tick_duration_ms_default: number;
/** prefetch 청크 크기(tick). */
Expand Down Expand Up @@ -114,14 +141,23 @@ export type LifecycleEventType =
| 'SPOT_CONFIRMED'
| 'SPOT_STARTED'
| 'SPOT_COMPLETED'
| 'NO_SHOW';
| 'NO_SHOW'
| 'PERSONA_LEAVE_SPOT'
| 'PERSONA_RETURN_HOME';

export type LifecycleEvent = {
tick: number;
event_type: LifecycleEventType;
spot_id: string;
/** NO_SHOW 등 agent 가 특정될 때만. */
/** NO_SHOW / PERSONA_* 등 agent 가 특정될 때만. */
agent_id?: string;
payload?: Record<string, unknown>;
scheduled_tick?: number;
schedule_lead_ticks?: number;
duration_ticks?: number;
expected_closed_at_tick?: number;
map_anchor?: SimMapAnchor;
hotspot_signal?: SimHotspotSignal;
};

export type LifecycleChunk = {
Expand Down
1 change: 1 addition & 0 deletions src/features/map/client/MapClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ export function MapClient() {
manifest: sim.manifest,
isReady: sim.isReady,
currentTick: sim.currentTick,
currentCycle: sim.currentCycle,
subscribe: sim.subscribe,
positionsRef: sim.positionsRef,
playbackStartMsRef: sim.playbackStartMsRef,
Expand Down
3 changes: 3 additions & 0 deletions src/features/map/model/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { SimHotspotSignal } from '@/entities/spot/sim-stream-types';
import type { GeoCoord } from '@/entities/spot/types';

export type PersonaRef = {
Expand Down Expand Up @@ -29,6 +30,8 @@ export type ActivityCluster = {
variant?: 'discovery' | 'ai-feed' | 'user-feed' | 'feed-group' | 'mine';
/** 변형에 따른 추가 라벨(예: "내 모임"). */
variantLabel?: string;
/** simulator-origin hotspot 신호. AI 피드 본문이 아니라 지도 발견/형성 상태용 메타데이터. */
hotspotSignal?: SimHotspotSignal;
};

export type ClusterInput = {
Expand Down
17 changes: 13 additions & 4 deletions src/features/simulation/model/sim-clock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@ export function findNextMovement(
return timeline[lo];
}

function placeCoord(place: {
lat: number;
lng: number;
map_anchor?: GeoCoord;
}): GeoCoord {
return place.map_anchor ?? { lat: place.lat, lng: place.lng };
}

type PositionAtOptions = {
fromCoord?: GeoCoord;
};
Expand All @@ -82,19 +90,20 @@ export function positionAt(
const to = placeMap.get(m.to_place_id);
if (!from || !to) return null;

const fromCoord = options?.fromCoord ?? { lat: from.lat, lng: from.lng };
const fromCoord = options?.fromCoord ?? placeCoord(from);
const toCoord = placeCoord(to);

if (tFloat <= m.depart_tick) {
return fromCoord;
}
if (tFloat >= m.arrive_tick) {
return { lat: to.lat, lng: to.lng };
return toCoord;
}
const span = m.arrive_tick - m.depart_tick;
const p = span <= 0 ? 1 : (tFloat - m.depart_tick) / span;
return {
lat: lerp(fromCoord.lat, to.lat, p),
lng: lerp(fromCoord.lng, to.lng, p),
lat: lerp(fromCoord.lat, toCoord.lat, p),
lng: lerp(fromCoord.lng, toCoord.lng, p),
};
}

Expand Down
Loading
Loading