最终幻想14水晶世界吧 关注:11,552贴子:135,983

以程序员角度分析Roll房bug

只看楼主收藏回复

本人程序员一枚,结合日常工作经验和此次bug的表象,以下仅为本人的推理猜想,若有不同意见,欢迎评论补充。
以下假设某L房有8人竞拍
0点0分0秒,系统开始第一轮抽签,号码完全随机,在1-8号中产生
产生随机号5,即5号竞拍者中签,5号中签者可以购买证书进行房屋搭建
然而系统的抽签并未终止,抽签系统出现bug,进行重新抽签
此时开始,抽签号码并非随机产生,而是在之前的号上递增,即第二轮抽签号码为6
此时6号竞拍者获得房屋搭建权,5号中签者失去房屋搭建权,若手速较快,已完成房屋搭建,则房产传递给6号
而这并不是终点,依旧进行新一轮抽签,第三轮抽签号码为7,相应的6号中签者失去所有权,7号顺位继承
以此类推,到抽中8号签时,抽签系统终止运行,最终8号竞拍者获得土地和房屋所有权,且不再变更
问:为什么有的人退了竞拍费,有的人却没退?
答:以上述为例,假设第一轮随机号为5,那么5号-8号的竞拍者会依次判定中签,即使最终所有权归8号所有,但这些中签者都视为中签,不退费,而1号-4号的竞拍者可以获得退费。
问:为什么1号2号没退费,3号4号却退费了?
答:虽然1号-4号都属于第一次随机抽签5号的前排,但是1号2号是以部队身份抽签的,而在5号-8号之间,也有该部队的人员以部队身份抽签,所以判定部队房抽签成功,1号2号不退费。
问:部队4个人抽签,1个人中签,其他3个人费用为什么不退?
答:部队身份多票抽签,相当于多花钱增加票数,提高抽签命中概率,及个人身份8分之1概率中签,部队身份8分之4概率中签,可变相理解为无上限竞拍金额,虽然单票上限2500W金币,但若部队20人投票,相当于花了5个亿,大大提高中签概率,若中签后只扣除中签号2500W,其余全部返还,对个人身份及人数较少部队会产生极大的不公平,所以以部队身份抽签的金币或水晶,部队中签后,全部不返还。(个人表示理解该设定,但不排除是抽签系统结算bug)


IP属地:安徽1楼2026-01-23 14:50回复
    问:为什么有的人退了竞拍费,有的人却没退?
    答:以上述为例,假设第一轮随机号为5,那么5号-8号的竞拍者会依次判定中签,即使最终所有权归8号所有,但这些中签者都视为中签,不退费,而1号-4号的竞拍者可以获得退费。


    IP属地:安徽2楼2026-01-23 14:51
    回复
      2026-04-18 22:59:19
      广告
      不感兴趣
      开通SVIP免广告
      问:为什么1号2号没退费,3号4号却退费了?
      答:虽然1号-4号都属于第一次随机抽签5号的前排,但是1号2号是以部队身份抽签的,而在5号-8号之间,也有该部队的人员以部队身份抽签,所以判定部队房抽签成功,1号2号不退费。


      IP属地:安徽3楼2026-01-23 14:51
      收起回复
        问:部队4个人抽签,1个人中签,其他3个人费用为什么不退?
        答:部队身份多票抽签,相当于多花钱增加票数,提高抽签命中概率,及个人身份8分之1概率中签,部队身份8分之4概率中签,可变相理解为无上限竞拍金额,虽然单票上限2500W金币,但若部队20人投票,相当于花了5个亿,大大提高中签概率,若中签后只扣除中签号2500W,其余全部返还,对个人身份及人数较少部队会产生极大的不公平,所以以部队身份抽签的金币或水晶,部队中签后,全部不返还。(个人表示理解该设定,但不排除是抽签系统结算bug)


        IP属地:安徽4楼2026-01-23 14:51
        回复
          没测出来的原因是,触发roll房后,得到了抽中结果,登录获奖者账号,可创建房屋。ok测试完毕


          IP属地:天津6楼2026-01-23 15:09
          回复
            import java.util.*;
            import java.util.concurrent.*;
            import java.util.concurrent.atomic.AtomicInteger;
            /**
            * 竞拍系统Bug模拟器
            * 模拟8人竞拍,抽签系统异常导致的所有权多次转移问题
            */
            public class AuctionBugSimulator {
            // 竞拍者信息
            static class Bidder {
            int bidderId;
            String type; // "个人" 或 "部队"
            boolean refunded = false;
            boolean won = false;
            public Bidder(int bidderId, String type) {
            this.bidderId = bidderId;
            this.type = type;
            }
            }
            // 竞拍状态
            static class AuctionState {
            int currentWinner = -1;
            int round = 0;
            List<Integer> winnerSequence = new ArrayList<>();
            Map<Integer, Boolean> refundStatus = new ConcurrentHashMap<>();
            }
            private static final int TOTAL_BIDDERS = 8;
            private static AuctionState state = new AuctionState();
            private static List<Bidder> bidders = new ArrayList<>();
            private static AtomicInteger bugTriggerCount = new AtomicInteger(0);
            public static void main(String[] args) throws InterruptedException {
            initializeBidders();
            simulateAuctionBug();
            printResults();
            }
            /**
            * 初始化竞拍者:1-2号为部队,3-8号为个人
            */
            private static void initializeBidders() {
            // 部队身份竞拍者
            bidders.add(new Bidder(1, "部队"));
            bidders.add(new Bidder(2, "部队"));
            // 个人身份竞拍者
            for (int i = 3; i <= TOTAL_BIDDERS; i++) {
            bidders.add(new Bidder(i, "个人"));
            }
            // 初始化退款状态
            for (int i = 1; i <= TOTAL_BIDDERS; i++) {
            state.refundStatus.put(i, false);
            }
            }
            /**
            * 模拟竞拍Bug的核心逻辑
            */
            private static void simulateAuctionBug() throws InterruptedException {
            ExecutorService executor = Executors.newFixedThreadPool(4);
            // 第一轮:正常随机抽签(产生5号)
            executor.submit(() -> {
            System.out.println("🚀 第一轮抽签开始...");
            int firstWinner = randomDraw(1, TOTAL_BIDDERS);
            state.currentWinner = firstWinner;
            state.winnerSequence.add(firstWinner);
            state.round++;
            System.out.println("🎯 第一轮随机抽签结果: " + firstWinner + "号中签");
            // Bug触发:系统异常重新抽签
            triggerBugSequence(firstWinner);
            });
            executor.shutdown();
            executor.awaitTermination(5, TimeUnit.SECONDS);
            // 模拟退款逻辑
            processRefunds();
            }
            /**
            * Bug触发序列:从初始中签号开始递增抽签
            */
            private static void triggerBugSequence(int startWinner) {
            int current = startWinner;
            // Bug现象:非随机递增抽签
            while (current < TOTAL_BIDDERS) {
            try {
            Thread.sleep(100); // 模拟抽签间隔
            current++;
            state.currentWinner = current;
            state.winnerSequence.add(current);
            state.round++;
            bugTriggerCount.incrementAndGet();
            System.out.println("🐛 第" + state.round + "轮Bug抽签: " + current + "号中签");
            // 模拟中签者进行房屋搭建
            simulateHouseBuilding(current);
            } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            break;
            }
            }
            System.out.println("⏹️ 抽签系统终止,最终获胜者: " + state.currentWinner + "号");
            }
            /**
            * 模拟房屋搭建过程(可能在中途被下一个中签者打断)
            */
            private static void simulateHouseBuilding(int winnerId) {
            Bidder winner = getBidder(winnerId);
            if (winner != null) {
            winner.won = true;
            System.out.println("🏠 " + winnerId + "号(" + winner.type + ")开始房屋搭建...");
            // 模拟搭建时间,可能被下一个中签者打断
            if (winnerId < TOTAL_BIDDERS) {
            System.out.println("⚠️ " + winnerId + "号搭建被" + (winnerId + 1) + "号继承");
            }
            }
            }
            /**
            * 处理退款逻辑(Bug的核心体现)
            */
            private static void processRefunds() {
            System.out.println("\n💰 开始处理退款...");
            int firstRandomWinner = state.winnerSequence.get(0); // 第一轮随机抽签的5号
            for (Bidder bidder : bidders) {
            // 规则1: 最终获胜者不退费
            if (bidder.bidderId == state.currentWinner) {
            bidder.refunded = false;
            System.out.println("❌ " + bidder.bidderId + "号(" + bidder.type + "): 最终获胜者,不退费");
            continue;
            }
            // 规则2: 曾经中签过的都不退费(5-7号)
            if (state.winnerSequence.contains(bidder.bidderId)) {
            bidder.refunded = false;
            System.out.println("❌ " + bidder.bidderId + "号(" + bidder.type + "): 曾中签,不退费");
            continue;
            }
            // 规则3: 部队身份特殊处理
            if ("部队".equals(bidder.type)) {
            // 检查该部队是否有其他人中签
            boolean comradeWon = bidders.stream()
            .filter(b -> "部队".equals(b.type) && b.won)
            .findAny()
            .isPresent();
            if (comradeWon) {
            bidder.refunded = false;
            System.out.println("❌ " + bidder.bidderId + "号(部队): 部队中签,全员不退费");
            continue;
            }
            }
            // 其他情况退费
            bidder.refunded = true;
            System.out.println("✅ " + bidder.bidderId + "号(" + bidder.type + "): 退费成功");
            }
            }
            // 辅助方法
            private static int randomDraw(int min, int max) {
            return new Random().nextInt(max - min + 1) + min;
            }
            private static Bidder getBidder(int id) {
            return bidders.stream().filter(b -> b.bidderId == id).findFirst().orElse(null);
            }
            private static void printResults() {
            System.out.println("\n" + "=".repeat(50));
            System.out.println("📊 竞拍结果总结");
            System.out.println("=".repeat(50));
            System.out.println("🔄 中签顺序: " + state.winnerSequence);
            System.out.println("🏆 最终获胜者: " + state.currentWinner + "号");
            System.out.println("🐛 Bug触发次数: " + bugTriggerCount.get());
            System.out.println("\n👥 竞拍者详情:");
            for (Bidder bidder : bidders) {
            String status = bidder.refunded ? "✅ 已退费" : "❌ 未退费";
            String winStatus = bidder.won ? " (曾中签)" : "";
            System.out.printf(" %d号(%s): %s%s\n",
            bidder.bidderId, bidder.type, status, winStatus);
            }
            // 验证Bug现象
            System.out.println("\n🔍 Bug验证:");
            System.out.println("• 1-2号(部队): 因部队中签而不退费 ← 规则争议点");
            System.out.println("• 3-4号(个人): 未中签且非最终获胜者,正常退费");
            System.out.println("• 5-7号(个人): 曾中签但不退费 ← Bug核心体现");
            System.out.println("• 8号(个人): 最终获胜者,不退费");
            }
            }


            IP属地:天津8楼2026-01-23 15:15
            收起回复
              我更觉得是分布式锁超时与状态机混乱:抽奖服务实例A获取了锁,完成了第一轮随机(假设选中5号),并开始处理后续事务(如通知、日志记录)。此时,如果实例A因GC(垃圾回收)停顿、网络延迟或处理缓慢,导致持有的分布式锁过期,实例B会立即获取锁并开始执行。实例B可能错误地认为自己是“继续”抽奖,而非“重新开始”,从而读取了实例A留下的不完整中间状态(如当前最大ID=5),并在此基础上递增。这就造成了抽奖序列的“规律化”。。。


              IP属地:天津10楼2026-01-23 15:27
              回复
                我服了,真tm是人才,现在气得只想爆破光子总部


                IP属地:四川来自Android客户端11楼2026-01-23 15:31
                回复
                  2026-04-18 22:53:19
                  广告
                  不感兴趣
                  开通SVIP免广告
                  我的房区有7个人 其中第一轮显示1号中奖 第二轮显示6号中奖 我的号分别问3-7 。除了6都退钱了


                  IP属地:江苏来自iPhone客户端12楼2026-01-23 15:47
                  收起回复
                    这游戏部队房本来就对于个人房不公平,在没出抽选系统房子全靠手速抢的时候,部队房优先两天选择,个人房后面才开放选择,所以部队多人参与但其他人退费这个设定才比较符合历史做法


                    IP属地:北京13楼2026-01-23 16:13
                    收起回复
                      我觉得是部署得时候,这类抽签任务没有单例/隔离运行,导致多个脚本同时对一个房屋抽签,这可以解释同时多个人中奖的问题。但为什么最终都显示最后那个人,我无法理解


                      IP属地:北京来自iPhone客户端14楼2026-01-23 17:04
                      收起回复
                        我是听说腾讯招聘要求非常高的,为什么这种策划,程序员能进腾讯,职业设计也很失败,凭关系进的吗?


                        IP属地:上海来自Android客户端15楼2026-01-23 17:21
                        收起回复
                          哥们一看就没玩过端游啊,部队所有人入一套房概率是大。那你得愿意买呀,你不能说所有人去购买一套房,然后如果队伍中标了,把部队所有人的资源都吃掉。那不扯淡吗?


                          IP属地:北京来自Android客户端16楼2026-01-23 17:59
                          回复
                            我猜这项目组现在连个正式的程序员都没了,这流水怎样得起啊🥴


                            IP属地:中国香港来自Android客户端17楼2026-01-23 19:03
                            回复
                              2026-04-18 22:47:19
                              广告
                              不感兴趣
                              开通SVIP免广告
                              这个游戏太搞了,野队打本困难程度对标逆水寒,神秘商店只有那两套商店平替比较划算,刷不出来。pvp体验糟糕,房屋系统只抽尾号。已经准备跑路了8888


                              IP属地:重庆来自Android客户端18楼2026-01-23 22:11
                              回复