12. 플레이어 이동 좌표 검증 (속도핵/벽통과 방지)

난이도 중 해설 보기 →

결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커 (A)(B) 는 주목 위치 힌트다.

결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 플레이어 이동 좌표 검증 (속도핵/벽통과 방지, 서버 권위)
// ----------------------------------------------------------------------------
// 시나리오:
//   - 플레이어가 움직이면 클라이언트가 C_Move 패킷을 보낸다.
//     payload = [float x][float y][float z][long clientTimeMs][float clientSpeed]
//   - 서버는 이동이 "물리적으로 가능한지" 검증한 뒤 위치를 갱신해야 한다.
//       (1) 직전 위치→새 위치 거리가 (허용속도 × 경과시간) 이내인가 (속도핵 방지)
//       (2) 목적지/경로가 통행 가능한가 (벽/장애물/낭떠러지 통과 방지)
//       (3) 순간이동(텔레포트)은 서버가 허가한 경우(포탈/스킬)에만 허용
//   - 클라이언트는 신뢰할 수 없다(좌표·시간·속도 모두 변조 가능).
//   - 같은 플레이어의 이동 패킷이 여러 IO 스레드에서 거의 동시에 처리될 수 있다.
//
// 요구사항:
//   - 위치는 서버가 권위 있게 갱신한다. 불가능한 이동은 거부하고 위치를 보정한다.
//   - 경과시간/속도는 서버 시계와 서버가 아는 캐릭터 속도로만 계산한다.
//   - 통행 불가 지점/벽 너머로의 이동을 막는다.
//
// 과제:
//   이 코드의 잠재적 문제를 모두 찾고, 어떻게 악용되는지 설명하고,
//   수정안과 더 나은 설계를 제시하라. (먼저 직접 리뷰 후 answer.md 와 대조)
// ============================================================================

using System;
using System.Collections.Generic;

public struct Vec3
{
    public float X, Y, Z;
    public Vec3(float x, float y, float z) { X = x; Y = y; Z = z; }
}

public class Player
{
    public long  Id;
    public Vec3  Pos;
    public long  LastMoveMs;        // 마지막 이동 처리 시각
    public float MoveSpeed;         // 서버가 아는 캐릭터 이동속도(units/sec)
    public bool  TeleportGranted;   // 서버가 발급한 1회성 순간이동 허가
}

public class CollisionMap
{
    // 해당 좌표가 통행 가능한지(서버 권위 맵 데이터). 구현은 생략.
    public bool IsWalkable(Vec3 p) { return true; }
}

public class MoveService
{
    private readonly Dictionary<long, Player> _players;
    private readonly CollisionMap _map;

    public MoveService(Dictionary<long, Player> players, CollisionMap map)
    {
        _players = players;
        _map = map;
    }

    private static long NowMs() => DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();

    private static float Distance(Vec3 a, Vec3 b)
    {
        float dx = a.X - b.X, dy = a.Y - b.Y, dz = a.Z - b.Z;
        return (float)Math.Sqrt(dx * dx + dy * dy + dz * dz);
    }

    // C_Move 처리. 위치 갱신에 성공하면 true.
    public bool OnMove(long playerId, float x, float y, float z,
                       long clientTimeMs, float clientSpeed)
    {
        Player p = _players[playerId];
        Vec3 dst = new Vec3(x, y, z);

        // (A) 속도 검증: 클라가 보낸 경과시간/속도로 허용 거리 계산
        long dtMs = clientTimeMs - p.LastMoveMs;
        float maxDist = clientSpeed * (dtMs / 1000f);
        float dist = Distance(p.Pos, dst);
        if (dist > maxDist)
            return false;

        // (B) 목적지 통행 가능 여부 — 그대로 신뢰하고 검사하지 않음

        // (C) 위치/시각 갱신: 클라가 보낸 시각을 그대로 저장
        p.Pos = dst;
        p.LastMoveMs = clientTimeMs;
        return true;
    }

    // 포탈/블링크 스킬이 호출. 다음 1회 이동을 순간이동으로 허용한다.
    public void GrantTeleport(long playerId)
    {
        _players[playerId].TeleportGranted = true;
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

작성 후 위 해설 보기에서 모범 해설과 대조하세요.