@@ -88,7 +88,272 @@ dddfasdsasdasdd
8888
8989### 분석 과정
9090
91- .
91+ ![ _ ] ( /assets/img/posts/2025-10-28-14.png ) {: style="max-width: 100%; height: auto;"}
92+
93+ ` tosu-1 ` 문제와 로직 나머지는 비슷하지만, 올 콤보를 해야하는것이 아닌 특정 값을 맞춰야 하는 문제입니다.
94+
95+ ` GIVEN ` 값은 변하지 않으며, ` CALCED ` 값은 지난 성공 처리하던 곳에 인자로 들어갑니다.
96+
97+ ![ _ ] ( /assets/img/posts/2025-10-28-15.png ) {: style="max-width: 100%; height: auto;"}
98+
99+ ![ _ ] ( /assets/img/posts/2025-10-28-16.png ) {: style="max-width: 100%; height: auto;"}
100+
101+ 점수 타입 및 노트 인덱스를 이용해 연산하는 로직이 보입니다. ` 4c00 ` 함수와 ` 1670 ` 함수, ` 53c0 ` 함수 및 초기값, 기대값을 ai에 넣어주면 바로 풀어주긴 합니다만 손으로 직접 다시 풀어보겠습니다.
102+
103+ ai가 짜준 코드는 아래와 같습니다.
104+
105+ ``` python
106+ # -*- coding: utf-8 -*-
107+ # HIT/MISS 스케줄을 계산하는 레퍼런스 구현
108+ # - 상태는 128바이트
109+ # - M: out[p] = in[p-1] XOR in[p+1] (경계 밖은 0)
110+ # - HIT: 위치 (t & 0x7F)에 c_t = (t*0x1B + 0x37) & 0xFF 를 XOR한 뒤 M
111+ # - MISS: M 만 수행
112+ # - 최종 상태를 목표 바이트열(Target)과 같게 만드는 h_t ∈ {0,1} 시퀀스를 Gaussian 소거로 풂 (GF(2))
113+
114+ from typing import List, Tuple
115+
116+ L = 128 # bytes
117+ MASK_128 = (1 << L) - 1
118+
119+ # ---------- 비트-평면 표현 도우미 ----------
120+
121+ def bytes_to_planes (bs : bytes ) -> List[int ]:
122+ """ 128바이트 -> 8개 비트평면(각 128비트 int). plane[b]의 bit p는 bs[p]의 b번째 비트."""
123+ assert len (bs) == L
124+ planes = [0 ]* 8
125+ for p, val in enumerate (bs):
126+ for b in range (8 ):
127+ if (val >> b) & 1 :
128+ planes[b] |= (1 << p)
129+ return planes
130+
131+ def planes_to_bytes (planes : List[int ]) -> bytes :
132+ """ 8개 비트평면 -> 128바이트"""
133+ out = bytearray (L)
134+ for p in range (L):
135+ v = 0
136+ for b in range (8 ):
137+ if (planes[b] >> p) & 1 :
138+ v |= (1 << b)
139+ out[p] = v & 0x FF
140+ return bytes (out)
141+
142+ def pack_planes_to_bigint (planes : List[int ]) -> int :
143+ """ 8개 평면을 1024비트 정수로 패킹(plane 0이 낮은 비트 구간). 열(column) 벡터로 취급한다."""
144+ big = 0
145+ for b in range (8 ):
146+ big ^= (planes[b] << (b * L))
147+ return big
148+
149+ def bigint_to_planes (big : int ) -> List[int ]:
150+ """ 1024비트 정수를 8개 평면으로 역변환"""
151+ planes = []
152+ for b in range (8 ):
153+ planes.append( (big >> (b * L)) & MASK_128 )
154+ return planes
155+
156+ # ---------- 변환 M (FUN_1400053c0) ----------
157+
158+ def mix_planes (planes : List[int ]) -> List[int ]:
159+ """ M: out[p] = in[p-1] XOR in[p+1] on each bit-plane with zero boundary."""
160+ out = []
161+ for b in range (8 ):
162+ x = planes[b]
163+ out.append( ((x << 1 ) ^ (x >> 1 )) & MASK_128 )
164+ return out
165+
166+ def advance_planes (planes : List[int ], steps : int ) -> List[int ]:
167+ """ M^steps (naive). T가 매우 크면 시간이 늘어남."""
168+ cur = planes[:]
169+ for _ in range (steps):
170+ cur = mix_planes(cur)
171+ return cur
172+
173+ # ---------- 한 스텝 HIT 효과 U_t 계산 ----------
174+
175+ def hit_injection_vector (t : int ) -> bytes :
176+ """ U_t: 위치 (t & 0x7F)에 c_t = (t*0x1B + 0x37) & 0xFF 만 비트가 있는 128바이트"""
177+ j = t & 0x 7F
178+ c = (t * 0x 1B + 0x 37 ) & 0x FF
179+ bs = bytearray (L)
180+ bs[j] = c
181+ return bytes (bs)
182+
183+ # ---------- 가우스 소거 (GF(2))로 A*h = delta 풀기 ----------
184+ # A = [v_0 ... v_{T-1}] (1024비트 column 들), delta = 1024비트
185+
186+ def solve_gf2_columns (columns : List[int ], delta : int ) -> Tuple[bool , List[int ]]:
187+ """
188+ columns: 길이 T, 각 원소는 1024비트(int) column
189+ delta : 1024비트(int)
190+ 반환: (성공여부, h(0/1) 리스트)
191+ """
192+ T = len (columns)
193+ # basis: pivot_bit -> (col_vector, combination_bitmask)
194+ basis_vec = {} # pivot_bit -> int(1024b)
195+ basis_comb = {} # pivot_bit -> int(Tb), 원본 column들의 조합
196+ # 각 column에 대한 combination bitmask (초기엔 자기 자신만 1)
197+ combs = [1 << i for i in range (T)]
198+ # 빌드 (column basis 생성)
199+ for i, col in enumerate (columns):
200+ v = col
201+ c = combs[i]
202+ # pivot 낮은 비트부터/높은 비트부터 아무거나 가능. 여기선 높은 비트 우선.
203+ while v:
204+ p = v.bit_length() - 1 # pivot 위치
205+ if p in basis_vec:
206+ v ^= basis_vec[p]
207+ c ^= basis_comb[p]
208+ else :
209+ basis_vec[p] = v
210+ basis_comb[p] = c
211+ break
212+ # v==0 이면 기존 basis로 표현 가능(선형 종속)
213+ # delta를 basis로 소거
214+ w = delta
215+ sol_comb = 0
216+ while w:
217+ p = w.bit_length() - 1
218+ if p not in basis_vec:
219+ # 해 없음
220+ return False , []
221+ w ^= basis_vec[p]
222+ sol_comb ^= basis_comb[p]
223+ # sol_comb 의 각 비트가 해당 column(=스텝)을 사용할지(HIT) 결정
224+ hits = [ (sol_comb >> i) & 1 for i in range (T) ]
225+ return True , hits
226+
227+ # ---------- 메인: 스케줄 계산 ----------
228+
229+ def compute_schedule (
230+ T : int ,
231+ init_bytes : bytes ,
232+ target_bytes : bytes
233+ ) -> Tuple[bool , List[int ]]:
234+ """
235+ 주어진 T(스텝 수), 초기 상태(init_bytes), 타겟 상태(target_bytes)에 대해
236+ HIT/MISS 스케줄(h[t]∈{0,1})을 계산.
237+ """
238+ assert len (init_bytes) == L and len (target_bytes) == L
239+
240+ # 1) base = M^T(S0)
241+ base_planes = advance_planes(bytes_to_planes(init_bytes), T)
242+ base_big = pack_planes_to_bigint(base_planes)
243+
244+ # 2) columns v_t = M^{T-t}(U_t)
245+ columns_big = []
246+ for t in range (T):
247+ U_bytes = hit_injection_vector(t)
248+ U_planes = bytes_to_planes(U_bytes)
249+ V_planes = advance_planes(U_planes, T - t) # M^{T-t}
250+ V_big = pack_planes_to_bigint(V_planes)
251+ columns_big.append(V_big)
252+
253+ # 3) delta = TARGET ^ base
254+ target_big = pack_planes_to_bigint(bytes_to_planes(target_bytes))
255+ delta = target_big ^ base_big
256+
257+ # 4) Solve A*h = delta
258+ ok, hits = solve_gf2_columns(columns_big, delta)
259+ return ok, hits
260+
261+ # ---------- 시뮬레이터(검증용) ----------
262+
263+ def simulate_with_schedule (T : int , init_bytes : bytes , hits : List[int ]) -> bytes :
264+ """ 계산된 스케줄로 실제 최종 상태를 시뮬레이션해서 검증한다."""
265+ planes = bytes_to_planes(init_bytes)
266+ for t in range (T):
267+ if hits[t]:
268+ # inject U_t
269+ Ub = hit_injection_vector(t)
270+ Ubp = bytes_to_planes(Ub)
271+ # XOR
272+ for b in range (8 ):
273+ planes[b] ^= Ubp[b]
274+ # mix
275+ planes = mix_planes(planes)
276+ return planes_to_bytes(planes)
277+
278+ # ---------- 입력(초기값/타깃) 세팅 ----------
279+
280+ # 1) 초기 상태 (네가 준 16개 QWORD, little-endian 메모리 순서로 128바이트 구성)
281+ QWORDS_INIT = [
282+ 0x 2cfe542af6bcdfc9 , 0x f827a156b5343296 ,
283+ 0x c2cf199748081bd1 , 0x 6e26919c7c7bf2f9 ,
284+ 0x 8b5b00d8abffae1b , 0x 96e0cde33808f13d ,
285+ 0x c59babdd835b5d0b , 0x 6b9e07b75d498495 ,
286+ 0x f18c48ce17b46361 , 0x f9b94c2601dfe836 ,
287+ 0x dc46ed6a800e449c , 0x b67a2df488263a5b ,
288+ 0x 6621c22f7d4d5b15 , 0x e4b8d8381dc49605 ,
289+ 0x 82df48ce211e68a5 , 0x 1a6836a93b27b7e4 ,
290+ ]
291+ INIT_BYTES = b ' ' .join(q.to_bytes(8 , ' little' ) for q in QWORDS_INIT )
292+
293+ # 2) 타깃 상태 (예: DAT_140077660) — 네가 올린 128바이트를 그대로 넣으면 됨
294+ TARGET_HEX = """
295+ 14 64 42 4a 00 cc 14 34 21 43 26 82 98 11 2f 6f
296+ 3e 89 3a b8 d0 1c af 57 ef 3a 6b 23 dd 83 6c d8
297+ 3b ca c1 01 d4 30 52 a4 aa 8f e6 55 b6 c3 6d 1c
298+ 86 c9 2c 94 6b 7a 91 ae 31 e3 7e cd 8f e9 dd 1a
299+ d1 c5 d0 c0 81 6f ee 9b 78 b0 aa fd 75 80 db 90
300+ 55 ff fd 90 f9 04 e9 54 2a 94 ea be 0e ec 44 35
301+ 28 18 bd 86 51 88 44 b1 d6 2d 27 d5 1b 4d 8d d2
302+ df a4 f3 cf 94 aa 15 2d 8d 75 99 15 47 10 f7 08
303+ """
304+ TARGET_BYTES = bytes (int (x,16 ) for x in TARGET_HEX .split())
305+
306+ # 3) 노트 수(T) — 정확한 값으로 채워야 함!
307+ # (게임 내부: T = (DAT_140099008 - DAT_140099000) >> 4)
308+ T = 1080 # TODO : 여기에 실제 노트 개수를 넣으세요 (정확히!)
309+
310+ if __name__ == " __main__" :
311+ if T <= 0 :
312+ print (" T(노트 수)를 먼저 정확히 설정하세요." )
313+ else :
314+ ok, hits = compute_schedule(T, INIT_BYTES , TARGET_BYTES )
315+ if not ok:
316+ print (" [!] 주어진 T로는 정확히 맞출 수 없습니다 (선형계 해 없음). T 또는 타깃/초기값을 확인하세요." )
317+ else :
318+ print (" [+] HIT/MISS 시퀀스 산출 완료." )
319+ print (" 예: 첫 64스텝:" , ' ' .join(' 1' if h else ' 0' for h in hits[:64 ]))
320+ print (" " .join(" 1" if h else " 0" for h in hits))
321+ # 검증
322+ final_bytes = simulate_with_schedule(T, INIT_BYTES , hits)
323+ if final_bytes == TARGET_BYTES :
324+ print (" [OK] 시뮬레이션 검증 통과: 최종 128바이트가 Target과 정확히 일치합니다." )
325+ else :
326+ print (" [!] 시뮬레이션 검증 실패: 계산/입력 값을 점검하세요." )
327+ ```
328+
329+ ---
330+
331+ ![ _ ] ( /assets/img/posts/2025-10-28-17.png ) {: style="max-width: 100%; height: auto;"}
332+
333+ ` 1670 ` 함수는 ` CALCED + 0x80 ` 위치의 데이터만 건드리고 있는 것으로 보입니다. 계산용 버퍼인듯합니다. 직접적으로 건드리지 않아서 패스합니다. (인자값으로 '0' 혹은 '1'이 들어간것을 보니 노트 히트에 따른 기록을 하는 듯 합니다)
334+
335+ ![ _ ] ( /assets/img/posts/2025-10-28-18.png ) {: style="max-width: 100%; height: auto;"}
336+
337+ | 변수가 많아 ` calced + 1 ` 은 ` calcedP1 ` 로 바꿔두었습니다.
338+
339+ 로직이 좀 복잡해보일수 있지만, 선택된 블럭이 16개정도 반복된 후 do while루프를 돌고 있습니다.
340+
341+ ` if (calcedP1 + ...calced < 0x7f) ` 은 바운더리 관련 연산이여서 마지막 부분 제외하고는 true로 보고 계산하면 됩니다
342+
343+ 치환하면 다음과 같은 코드가 됩니다.
344+
345+ ``` python
346+ dat = [None ] * 128
347+ for i in range (128 ):
348+ # 바운더리 오류 날 경우 a나 b를 0으로 처리하면 됩니다.
349+ a = dat[i + 1 ]
350+ b = dat[i - 1 ]
351+ dat[i] = a ^ b
352+ ```
353+
354+ 이게 로직 전부인데, ai가 출력한 코드는 좀 깁니다. 솔버를 만드느라 그런 듯 합니다.
355+
356+ 위 함수 이용해서 ` z3 ` 솔버 이용했으면 됐었을것같습니다.
92357
93358## modulation-master
94359
0 commit comments